diff --git a/CMakeLists.txt b/CMakeLists.txt index 547eb733e7..bf561ede77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -397,7 +397,7 @@ target_link_libraries(lte_rrc PRIVATE nr_rrc) #NR RRC ####### add_library(nr_rrc ${OPENAIR2_DIR}/RRC/NR/MESSAGES/asn1_msg.c) -target_link_libraries(nr_rrc PUBLIC asn1_nr_rrc asn1_lte_rrc) +target_link_libraries(nr_rrc PUBLIC asn1_nr_rrc asn1_lte_rrc rrc_cell_management) # S1AP ############## diff --git a/common/utils/nr/nr_common.c b/common/utils/nr/nr_common.c index 96b8681cff..cfbb09e939 100644 --- a/common/utils/nr/nr_common.c +++ b/common/utils/nr/nr_common.c @@ -1487,3 +1487,35 @@ float get_beta_dmrs(int num_cdm_groups_no_data, bool is_type2) } return beta_dmrs_pusch; } + +/** @brief Construct full 5G-S-TMSI from 5G-S-TMSI components + * @param amf_set_id AMF Set ID (10 bits) + * @param amf_pointer AMF Pointer (6 bits) + * @param m_tmsi 5G-TMSI (32 bits) + * @return Full 5G-S-TMSI (48 bits) + * @note The 5G-S-TMSI is constructed as a 48-bit value: + * - Bits 38-47: AMF Set ID (10 bits) + * - Bits 32-37: AMF Pointer (6 bits) + * - Bits 0-31: 5G-TMSI (32 bits) + * @ref 3GPP TS 23.003 */ +uint64_t nr_construct_5g_s_tmsi(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi) +{ + // Construct full 5G-S-TMSI: <5G-TMSI (32 bits)> + return ((uint64_t)amf_set_id << 38) | ((uint64_t)amf_pointer << 32) | m_tmsi; +} + +/** @brief Construct 5G-S-TMSI-Part1 from 5G-S-TMSI components + * @param amf_set_id AMF Set ID (10 bits) + * @param amf_pointer AMF Pointer (6 bits) + * @param m_tmsi 5G-TMSI (32 bits) + * @return 5G-S-TMSI-Part1 (rightmost 39 bits of the full 5G-S-TMSI) + * @note 5G-S-TMSI-Part1 is the rightmost 39 bits of the full 5G-S-TMSI: + * - Bits 32-37: AMF Pointer (6 bits) + * - Bits 0-31: 5G-TMSI (32 bits) + * @ref 3GPP TS 23.003 */ +uint64_t nr_construct_5g_s_tmsi_part1(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi) +{ + // Construct full 5G-S-TMSI and extract Part1: rightmost 39 bits + uint64_t full_s_tmsi = nr_construct_5g_s_tmsi(amf_set_id, amf_pointer, m_tmsi); + return full_s_tmsi & ((1ULL << 39) - 1); +} diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index e62d66658c..42ea232140 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -357,6 +357,12 @@ frequency_range_t get_freq_range_from_band(uint16_t band); */ float get_beta_dmrs(int num_cdm_groups_no_data, bool is_type2); +/** @brief Construct full 5G-S-TMSI from 5G-S-TMSI components */ +uint64_t nr_construct_5g_s_tmsi(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi); + +/** @brief Construct 5G-S-TMSI-Part1 from 5G-S-TMSI components */ +uint64_t nr_construct_5g_s_tmsi_part1(uint16_t amf_set_id, uint8_t amf_pointer, uint32_t m_tmsi); + #define CEILIDIV(a,b) ((a+b-1)/b) #define ROUNDIDIV(a,b) (((a<<1)+b)/(b<<1)) #define BOUNDED_EVAL(a, b, c) (min(c, max(a, b))) diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index a2bcccd150..084e3b93f3 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -168,7 +168,7 @@ int fetch_du_by_ue_id(char *buf, int debug, telnet_printfunc_t prnt) nr_rrc_du_container_t *du = get_du_for_ue(RC.nrrrc[0], ue_id); if (du) { - prnt("gNB_DU_id %d is connected to ue_id %ld\n", du->setup_req->gNB_DU_id, ue_id); + prnt("gNB_DU_id %ld is connected to ue_id %ld\n", du->gNB_DU_id, ue_id); return 0; } else { ERROR_MSG_RET("No DU connected\n"); diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index f5c4956c9d..2299394e0c 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -390,13 +390,255 @@ sequenceDiagram ## Structures -### Cells +### DUs and Cells -OAI 5G RRC does not actually handle multiple cells as of now, but multiple DUs, -each being limited to one cell. +OAI 5G RRC is enabling support for multiple DUs, with each DU potentially +serving multiple cells (though currently each DU is limited to one cell in practice). +The architecture separates DU management from cell management to enable future +multi-cell per DU capability. -Cell-related data is stored in `nr_rrc_du_container_t`, and kept in a tree -indexed by the SCTP association ID. +#### DU Management + +DU-related data is stored in `nr_rrc_du_container_t`, and kept in a red-black +tree indexed by the unique SCTP association ID (`assoc_id`). Each DU container stores: +- DU identity and name +- SCTP association ID for F1 interface communication +- RRC version information +- Sequential array of cell pointers (`cells`) - stores pointers to cells belonging to this DU (seq_arr_t) + +Key Functions: +- `get_du_by_assoc_id()` - Lookup DU by SCTP association ID (O(log d) tree lookup) +- `get_du_for_ue()` - Get DU associated with a specific UE +- `find_target_du()` - Find a target DU for handover operations + +#### Cell Management + +Cell-related data is stored in `nr_rrc_cell_container_t`. Cells are stored in two data structures: + +1. Global cell tree (`rrc->cells`): Red-black tree indexed by `cell_id`, containing all cells from all DUs. Used for efficient O(log N_CELL) lookups across all DUs. PCI reuse across the network is allowed and this is reflected in the tree. +2. DU cell array (`du->cells`): Sequential array (seq_arr_t) of cell pointers, storing only cells belonging to that specific DU. Used for DU-specific operations. PCI must be unique within a DU. + +Each cell container stores: +- Cell identity (NR Cell ID) and PCI (Physical Cell ID) +- Link to serving DU via the unique `assoc_id` +- Cell-specific information (PLMN, TAC, frequency, mode TDD/FDD) +- MIB, SIB1, and MeasurementTimingConfiguration messages + +Key Functions: +- `get_cell_by_cell_id()` - Lookup cell by NR Cell ID using global cell tree +- `rrc_get_cell_for_du()` - Lookup cell by cell_id within a specific DU's cell array +- `rrc_get_cell_by_pci_for_du()` - Lookup cell by PCI within DU's cells array +- `rrc_add_cell_to_du()` - Add cell to DU's sequential array +- `rrc_free_cell_container()` - Free cell container and associated ASN.1 structures + +Architecture Notes: +- The `assoc_id` field (in cell and DUs containers) links cells to their serving DUs +- When a DU connects via F1 Setup, cells are added to both the global tree and the DU's array +- When a DU disconnects, cells are removed from both structures +- Global tree enables efficient cross-DU cell lookups (O(log N_CELL)) +- DU array enables efficient per-DU cell iteration (O(k) where k=cells per DU) +- Each DU maintains a sequential array of cell pointers + +##### DU and Cell Lifecycle + +The following diagram shows the lifecycle of DUs and their associated cells, including the main F1AP messages and internal operations: + +```mermaid +sequenceDiagram + participant CellTree as Cell Tree + participant DUTree as DU Tree + participant CU as CU-CP + participant DU as gNB-DU + participant UE as UE + + Note over DU,CellTree: DU Connection & Cell Registration + DU->>CU: F1AP F1 Setup Request(DU ID, Cell Info, MIB/SIB1) + Note over CU: Validate: PLMN match with CU configuration + alt PLMN mismatch + CU->>DU: F1AP F1 Setup Failure (PLMN not served) + end + Note over CU: Validate: DU ID uniqueness (RB_FOREACH gNB_DU_id in DU tree) + alt DU ID already exists + CU->>DU: F1AP F1 Setup Failure (Unspecified) + end + Note over CU: Validate: Neighbour cell configuration (if configured) + alt Neighbour config invalid + CU->>DU: F1AP F1 Setup Failure (Unspecified) + end + Note over CU: Extract MIB/SIB1 from system info (if present) + alt System info extraction fails + CU->>DU: F1AP F1 Setup Failure (Semantic error) + end + alt All Validations Success + CU->>CU: Create cell container (nr_rrc_cell_container_t), set assoc_id, copy cell info, set MIB/SIB1 + CU->>CellTree: rrc_add_cell(rrc, new) - RB_INSERT into global tree, increment rrc->num_cells + alt Duplicate cell_id (collision) + CU->>DU: F1AP F1 Setup Failure (Cell not available) + else Cell added to tree + CU->>CU: Create DU container (nr_rrc_du_container_t) + CU->>CU: seq_arr_init(&du->cells) - Initialize DU's cell array + CU->>DUTree: rrc_add_du(rrc, du) - RB_INSERT(du), increment rrc->num_dus + CU->>CU: rrc_add_cell_to_du(&du->cells, new) - Add cell to DU's array + Note over CU,UE: Cell available for UE association + CU->>CU: Encode CU SIBs (if configured) + CU->>DU: F1AP F1 Setup Response(Cells to Activate, CU SIBs) + Note over CU,CellTree: DU and cell now active + end + end + + UE->>CU: RRC Setup Request + Note over CU: rrc_handle_RRCSetupRequest() + CU->>CU: get_cell_by_cell_id(&rrc->cells) - Use global tree + alt Cell not found + CU->>UE: RRC Reject + end + Note over CU: UE Cell Association + CU->>CU: rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX) + + Note over DU,CellTree: Optional: Cell Configuration Update + opt DU sends configuration update + DU->>CU: F1AP DU Configuration Update(Add/Modify/Delete cells) + Note over CU: get_du_by_assoc_id(assoc_id) + loop For each cell to add + CU->>CU: get_cell_by_cell_id(&rrc->cells) - Check cell_id uniqueness globally + alt Duplicate cell_id found + Note over CU: Reject and return + end + CU->>CU: rrc_get_cell_by_pci_for_du(&du->cells) - Check PCI unique within DU + alt Duplicate PCI in DU + Note over CU: Reject and return + end + end + loop Cell Modification + CU->>CU: get_cell_by_cell_id(cells, old_nr_cellid) - Find cell by old cell_id + CU->>CU: update_cell_info(rrc, old_nci, new_ci) - Update in place + Note over CU: If cell_id changes: RB_REMOVE then re-insert after update + Note over CU: Free old MTC if new measurement timing config provided + Note over CU: If sys_info present: extract MIB/SIB1 and set on cell + end + CU->>DU: F1AP DU Configuration Update Acknowledge + end + + Note over DU,CellTree: DU Disconnection & Cell Cleanup + DU-->>CU: F1AP Lost Connection(SCTP connection lost) + Note over CU: rrc_CU_process_f1_lost_connection() + CU->>DUTree: RB_FIND(du) - Find DU by assoc_id using temporary struct + alt DU not found + Note over CU: Log warning and return + end + Note over CU: rrc_cleanup_du() then invalidate_du_connections() + CU->>CU: Iterate cells in DU's array (last to first) + loop For each cell in DU's array + CU->>CU: seq_arr_erase(&du->cells, cell_ptr) - Remove from DU's array + CU->>CellTree: rrc_rm_cell(): RB_REMOVE(cell), decrement num_cells, rrc_free_cell_container() + end + CU->>DUTree: rrc_rm_du(): RB_REMOVE(du) - Remove DU from tree + CU->>CU: Decrement rrc->num_dus counter + CU->>CU: seq_arr_free(&du->cells) - Free DU's cell array + CU->>CU: rrc_free_du_container() - Free DU container + Note over CU: invalidate_du_connections() + loop For each UE: + CU->>CU: rrc_remove_ue_scells_from_du() - Remove SCells from disconnected DU + alt UE belongs to disconnected DU + CU->>CU: Set du_assoc_id = 0 (mark DU offline) + CU->>CU: Trigger NGAP UE Context Release Request + end + end + Note over CU,CellTree: DU and all cells removed +``` + +Key Functions: +- `rrc_gNB_process_f1_setup_req()` - Handles F1 Setup Request, creates DU and cell containers. + Validates PLMN match, DU ID uniqueness, and cell_id/PCI uniqueness before creating containers. +- `rrc_gNB_process_f1_du_configuration_update()` - Handles cell configuration updates. + Currently supports cell modification (MIB/SIB1 updates) and validates cell additions, but cell addition + and deletion are not yet fully implemented. +- `rrc_CU_process_f1_lost_connection()` - Handles DU disconnection. Calls `rrc_cleanup_du()` to + remove all cells and the DU from their trees and free resources, then + `invalidate_du_connections()` to clean up UE associations (e.g. trigger NGAP UE Context Release Request for UEs on that DU). + +#### UE Cell Association Management + +The RRC maintains a per-UE association with serving cells, tracking which cells +a UE is currently using. This replaces the previous single-cell assumption and +enables proper multi-cell support where each UE can have multiple serving cells +(one PCell and up to 31 SCells). The servCellIndex (TS 38.331) is tracked per-UE +in the `ue_serving_cell_t` structure in the UE context (the same cell can have +different servCellIndex values for different UEs). + +Data Structures: +- `ue_serving_cell_t`: Stores serving cell information (nci, servCellIndex, assoc_id) +- `gNB_RRC_UE_t.serving_cells`: Dynamic array (seq_arr_t) of serving cell entries. PCell is always at index 0. + +Key Functions: +- `rrc_add_ue_serving_cell()` - Adds a new serving cell to UE's serving_cells array. +- `rrc_get_ue_serving_cell_by_id()` - Retrieves serving cell entry by servCellIndex. +- `ue_get_pcell_entry()` - Returns the PCell serving cell entry (first element in serving_cells). +- `rrc_remove_ue_scells_from_du()` - Removes all serving cells belonging to a specific DU via assoc_id (e.g. during handover or DU disconnection). + +##### Handover and Cell Association Updates + +During handover, the UE's serving cell list is updated so the PCell reflects the target cell and source-DU cells are removed. + +###### F1 handover (inter-DU, same CU-CP) + +Cell association is updated when the **source** DU sends F1AP UE Context Modification Response (after it has sent the RRC Reconfiguration to the UE). The CU-CP is a single RRC instance; both source DU and target DU are under the same CU. + +```mermaid +sequenceDiagram + participant SourceDU as Source DU + participant CU as CU-CP + + Note over CU,CU: F1 handover: cell association update on Context Modification Response + SourceDU->>CU: F1AP UE Context Modification Response + Note over CU: rrc_CU_process_ue_context_modification_response() + Note over CU: Check: ho_context && source && target (F1 HO) + + alt F1 handover in progress + CU->>CU: nr_rrc_apply_target_context(UE) + Note over CU: F1 UE data: du_assoc_id = target DU, secondary_ue = target DU UE ID, RNTI = target RNTI + CU->>CU: nr_rrc_update_cell_assoc_after_ho(rrc, UE) + Note over CU: F1 branch (ho_context->source present): + CU->>CU: rrc_remove_ue_scells_from_du(UE, source_ctx->cell->assoc_id) + Note over CU: Remove all serving cells (incl. PCell) belonging to source DU + CU->>CU: rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX) + Note over CU: Handover complete: PCell = target cell, source DU cells removed + end +``` + +###### N2 handover (inter-gNB: source CU vs target CU) + +Cell association is updated only on the target CU-CP, when the target DU sends F1AP UE Context Setup Response. The UE context was created for handover, so there is no existing serving cell to remove. The flow is triggered inside the handover request acknowledge callback. + +- Target CU-CP: Receives HANDOVER REQUEST, sets up bearer and F1 UE context on target DU; when the target DU sends F1AP UE Context Setup Response, the target CU runs the cell-association update and then sends HANDOVER REQUEST ACKNOWLEDGE. + +```mermaid +sequenceDiagram + participant TargetDU as Target DU + participant TargetCU as Target CU-CP + + Note over TargetCU: N2 handover: cell association update + Note over TargetCU: F1AP UE Context Setup Resp from target DU (in nr_rrc_n2_ho_acknowledge) + TargetDU->>TargetCU: F1AP UE Context Setup Response + Note over TargetCU: rrc_CU_process_ue_context_setup_response() then callback ho_req_ack() + Note over TargetCU: nr_rrc_n2_ho_acknowledge(rrc, UE) + + TargetCU->>TargetCU: nr_rrc_apply_target_context(UE) + Note over TargetCU: F1 UE data: du_assoc_id = target DU, secondary_ue = target DU UE ID, RNTI = target RNTI + TargetCU->>TargetCU: nr_rrc_update_cell_assoc_after_ho(rrc, UE) + Note over TargetCU: N2 branch (ho_context->source NULL): no SCells to remove + TargetCU->>TargetCU: rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX) + Note over TargetCU: Then: encode Handover Command, send NGAP HANDOVER REQUEST ACKNOWLEDGE +``` + +## UE Context + +UE context information is stored in `gNB_RRC_UE_t`, which includes: +- Serving cells tracking: Dynamic array of serving cells (PCell + SCells) +- Security context: Keys, algorithms, and security state +- Radio bearers: SRB and DRB configurations +- PDU sessions: Active PDU session information +- Handover context: Temporary data during handover procedures ### CU-UPs diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index baeee1480d..0794d8b804 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -41,15 +41,19 @@ For each DU, it prints: ``` 1 connected DUs -[1] DU ID 3584 (gNB-OAI-DU) assoc_id 8: nrCellID 12345678, PCI 0, SSB ARFCN 641280 - TDD: band 78 ARFCN 640008 SCS 30 (kHz) PRB 106 +[1] DU ID 3584 (gNB-OAI) integrated DU-CU: 1 cell + [1] nrCellID 12345678, PCI 0, Mode TDD, SSB ARFCN 641280 + TDD: band 78 ARFCN 640008 SCS 30 (kHz) PRB 106 ``` -i.e., an index (`[1]`), the DU ID and it's name, the SCTP association ID -(`assoc_id 8`, cf. UE information), and DU specific information for the cell -(cell ID, physical cell identity/PCI, the SSB frequency in ARFCN notation, the -band and Point A ARFCN, subcarrier spacing/SCS, and the number of resource -blocks/PRB). Only one cell per DU is supported. +The output shows: +- Total number of connected DUs +- For each DU: an index (`[1]`), the DU ID and name, connection type (either `integrated DU-CU` for monolithic deployments or `assoc_id X` for split deployments), and the number of cells served by this DU +- For each cell served by the DU: a cell index (`[1]`), the NR Cell ID, Physical Cell ID (PCI), operating mode (TDD or FDD), and SSB ARFCN +- Cell-specific frequency information: for TDD mode, the band, Point A ARFCN, subcarrier spacing (SCS), and number of resource blocks (PRB); for FDD mode, separate DL and UL frequency information + +The RRC enables the support of multiple cells per DU (though currently each DU +typically serves one cell in practice). As of now, it does not print information about connected CU-UPs or AMFs. diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 0cd090ebc7..c891a44adc 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -74,12 +74,11 @@ */ #define F1AP_TRANSPORT_LAYER_ADDRESS_SIZE (160 / 8) - -// Note this should be 512 from maxval in 38.473 -#define F1AP_MAX_NB_CELLS 2 +/* F1 Setup Request (9.3.1.4 of 3GPP TS 38.473) */ +#define F1AP_MAX_NB_CELLS 512 #define F1AP_MAX_NO_OF_TNL_ASSOCIATIONS 32 -#define F1AP_MAX_NO_UE_ID 1024 +#define F1AP_MAX_NO_UE_ID 65536 /* 9.3.1.42 of 3GPP TS 38.473 - gNB-CU System Information */ #define F1AP_MAX_NO_SIB_TYPES 32 @@ -122,54 +121,53 @@ typedef struct f1ap_tdd_info_t { } f1ap_tdd_info_t; typedef struct f1ap_served_cell_info_t { - // NR CGI + // NR CGI (Mandatory) plmn_id_t plmn; uint64_t nr_cellid; // NR Global Cell Id - - // NR Physical Cell Ids + // NR PCI (Mandatory) uint16_t nr_pci; - - /* Tracking area code */ + // 5GS TAC (Optional) uint32_t *tac; - - // Number of slice support items (max 16, could be increased to as much as 1024) + // TAI Slice Support List (Optional) uint16_t num_ssi; nssai_t nssai[MAX_NUM_SLICES]; - + // NR-Mode-Info (Mandatory) f1ap_mode_t mode; union { f1ap_fdd_info_t fdd; f1ap_tdd_info_t tdd; }; - + // Measurement Timing Configuration (Mandatory) uint8_t *measurement_timing_config; int measurement_timing_config_len; } f1ap_served_cell_info_t; typedef struct f1ap_gnb_du_system_info_t { + // MIB message (Mandatory) uint8_t *mib; int mib_length; + // SIB1 message (Mandatory) uint8_t *sib1; int sib1_length; } f1ap_gnb_du_system_info_t; typedef struct f1ap_setup_req_s { - /// ulong transaction id + // Transaction ID (Mandatory) uint64_t transaction_id; - - // F1_Setup_Req payload + // gNB-DU ID (Mandatory) uint64_t gNB_DU_id; + // gNB-DU Name (Optional) char *gNB_DU_name; - - /// rrc version + // gNB-DU RRC version (Mandatory) uint8_t rrc_ver[3]; - - /// number of DU cells available - uint16_t num_cells_available; //0< num_cells_available <= 512; + // gNB-DU Served Cells List (0..1) + uint16_t num_cells_available; // (1..512) struct { + // Served Cell Information (Mandatory) f1ap_served_cell_info_t info; + // gNB-DU System Information (Optional) f1ap_gnb_du_system_info_t *sys_info; - } cell[F1AP_MAX_NB_CELLS]; + } *cell; } f1ap_setup_req_t; typedef struct f1ap_du_register_req_t { @@ -203,8 +201,8 @@ typedef struct f1ap_setup_resp_s { /// string holding gNB_CU_name char *gNB_CU_name; /// number of DU cells to activate - uint16_t num_cells_to_activate; //0< num_cells_to_activate <= 512; - served_cells_to_activate_t cells_to_activate[F1AP_MAX_NB_CELLS]; + uint16_t num_cells_to_activate; + served_cells_to_activate_t *cells_to_activate; /// rrc version uint8_t rrc_ver[3]; @@ -215,8 +213,8 @@ typedef struct f1ap_gnb_cu_configuration_update_s { /// Transaction ID uint64_t transaction_id; /// number of DU cells to activate - uint16_t num_cells_to_activate; //0< num_cells_to_activate/mod <= 512; - served_cells_to_activate_t cells_to_activate[F1AP_MAX_NB_CELLS]; + uint16_t num_cells_to_activate; + served_cells_to_activate_t *cells_to_activate; } f1ap_gnb_cu_configuration_update_t; typedef struct f1ap_setup_failure_s { @@ -236,7 +234,7 @@ typedef struct f1ap_gnb_cu_configuration_update_acknowledge_s { plmn_id_t plmn; uint64_t nr_cellid; uint16_t cause; - } cells_failed_to_be_activated[F1AP_MAX_NB_CELLS]; + } * cells_failed_to_be_activated; int have_criticality; uint16_t criticality_diagnostics; // gNB-CU TNL Association Setup List @@ -245,14 +243,14 @@ typedef struct f1ap_gnb_cu_configuration_update_acknowledge_s { // gNB-CU TNL Association Failed to Setup List uint16_t noofTNLAssociations_failed; f1ap_cp_tnl_t tnlAssociations_failed[F1AP_MAX_NO_OF_TNL_ASSOCIATIONS]; - // Dedicated SI Delivery Needed UE List + // Dedicated SI Delivery Needed UE List (max 65536 UE IDs) uint16_t noofDedicatedSIDeliveryNeededUEs; struct { uint32_t gNB_CU_ue_id; // NR CGI plmn_id_t ue_plmn; uint64_t ue_nr_cellid; - } dedicatedSIDeliveryNeededUEs[F1AP_MAX_NO_UE_ID]; + } * dedicatedSIDeliveryNeededUEs; } f1ap_gnb_cu_configuration_update_acknowledge_t; typedef struct f1ap_gnb_cu_configuration_update_failure_s { @@ -279,7 +277,7 @@ typedef struct f1ap_gnb_du_configuration_update_s { struct { f1ap_served_cell_info_t info; f1ap_gnb_du_system_info_t *sys_info; - } cell_to_add[F1AP_MAX_NB_CELLS]; + } * cell_to_add; /// int cells_to_modify uint16_t num_cells_to_modify; @@ -288,7 +286,7 @@ typedef struct f1ap_gnb_du_configuration_update_s { uint64_t old_nr_cellid; // NR Global Cell Id f1ap_served_cell_info_t info; f1ap_gnb_du_system_info_t *sys_info; - } cell_to_modify[F1AP_MAX_NB_CELLS]; + } * cell_to_modify; /// int cells_to_delete uint16_t num_cells_to_delete; @@ -296,9 +294,9 @@ typedef struct f1ap_gnb_du_configuration_update_s { // NR CGI plmn_id_t plmn; uint64_t nr_cellid; // NR Global Cell Id - } cell_to_delete[F1AP_MAX_NB_CELLS]; + } * cell_to_delete; - f1ap_cell_status_t status[F1AP_MAX_NB_CELLS]; + f1ap_cell_status_t *status; int num_status; /// gNB-DU unique ID, at least within a gNB-CU (0 .. 2^36 - 1) @@ -310,7 +308,7 @@ typedef struct f1ap_gnb_du_configuration_update_acknowledge_s { uint64_t transaction_id; /// number of DU cells to activate uint16_t num_cells_to_activate; // 0< num_cells_to_activate <= 512; - served_cells_to_activate_t cells_to_activate[F1AP_MAX_NB_CELLS]; + served_cells_to_activate_t *cells_to_activate; } f1ap_gnb_du_configuration_update_acknowledge_t; typedef struct f1ap_gnb_du_configuration_update_failure_s { diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index 2253493832..55b4972d4b 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -423,7 +423,7 @@ static F1AP_ProtocolExtensionContainer_11023P34_t *write_slice_info(int num_ssi, /** * @brief F1AP Setup Request memory management */ -void free_f1ap_cell(const f1ap_served_cell_info_t *info, const f1ap_gnb_du_system_info_t *sys_info) +static void free_f1ap_cell(const f1ap_served_cell_info_t *info, const f1ap_gnb_du_system_info_t *sys_info) { if (sys_info) { free(sys_info->mib); @@ -842,8 +842,9 @@ bool decode_f1ap_setup_request(const F1AP_F1AP_PDU_t *pdu, f1ap_setup_req_t *out /* GNB_DU_Served_Cells_List */ out->num_cells_available = ie->value.choice.GNB_DU_Served_Cells_List.list.count; AssertError(out->num_cells_available > 0, return false, "at least 1 cell must be present"); + out->cell = calloc_or_fail(out->num_cells_available, sizeof(*out->cell)); /* Loop over gNB-DU Served Cells Items */ - for (int i = 0; i < out->num_cells_available; i++) { + for (int i = 0; i < out->num_cells_available && i < F1AP_MAX_NB_CELLS; i++) { F1AP_GNB_DU_Served_Cells_Item_t *served_cells_item = &(((F1AP_GNB_DU_Served_Cells_ItemIEs_t *)ie->value.choice.GNB_DU_Served_Cells_List.list.array[i]) ->value.choice.GNB_DU_Served_Cells_Item); @@ -885,7 +886,7 @@ bool decode_f1ap_setup_request(const F1AP_F1AP_PDU_t *pdu, f1ap_setup_req_t *out return true; } -f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t *src) +static f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t *src) { f1ap_served_cell_info_t dst = { .plmn = src->plmn, @@ -952,7 +953,9 @@ f1ap_setup_req_t cp_f1ap_setup_request(const f1ap_setup_req_t *msg) cp.transaction_id = msg->transaction_id; /* num_cells_available */ cp.num_cells_available = msg->num_cells_available; - for (int n = 0; n < msg->num_cells_available; n++) { + if (cp.num_cells_available > 0) + cp.cell = calloc_or_fail(cp.num_cells_available, sizeof(*cp.cell)); + for (int n = 0; n < msg->num_cells_available && n < F1AP_MAX_NB_CELLS; n++) { /* cell.info */ cp.cell[n].info = copy_f1ap_served_cell_info(&msg->cell[n].info); /* cell.sys_info */ @@ -972,7 +975,7 @@ bool eq_f1ap_setup_request(const f1ap_setup_req_t *a, const f1ap_setup_req_t *b) _F1_EQ_CHECK_STR(a->gNB_DU_name, b->gNB_DU_name); _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); _F1_EQ_CHECK_INT(a->num_cells_available, b->num_cells_available); - for (int i = 0; i < a->num_cells_available; i++) { + for (int i = 0; i < a->num_cells_available && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_cell_info(&a->cell[i].info, &b->cell[i].info)) return false; if (!eq_f1ap_sys_info(a->cell[i].sys_info, b->cell[i].sys_info)) @@ -992,8 +995,11 @@ void free_f1ap_setup_request(const f1ap_setup_req_t *msg) { DevAssert(msg != NULL); free(msg->gNB_DU_name); - for (int i = 0; i < msg->num_cells_available; i++) { - free_f1ap_cell(&msg->cell[i].info, msg->cell[i].sys_info); + if (msg->cell != NULL) { + for (int i = 0; i < msg->num_cells_available; i++) { + free_f1ap_cell(&msg->cell[i].info, msg->cell[i].sys_info); + } + free(msg->cell); } } @@ -1032,15 +1038,16 @@ F1AP_F1AP_PDU_t *encode_f1ap_setup_response(const f1ap_setup_resp_t *msg) OCTET_STRING_fromBuf(&ie2->value.choice.GNB_CU_Name, msg->gNB_CU_name, strlen(msg->gNB_CU_name)); } // Cells to be Activated List (O) - for (int i = 0; i < msg->num_cells_to_activate; i++) { + if (msg->num_cells_to_activate > 0) { + DevAssert(msg->num_cells_to_activate <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_F1SetupResponseIEs_t, ie3); ie3->id = F1AP_ProtocolIE_ID_id_Cells_to_be_Activated_List; ie3->criticality = F1AP_Criticality_reject; ie3->value.present = F1AP_F1SetupResponseIEs__value_PR_Cells_to_be_Activated_List; - asn1cSequenceAdd(ie3->value.choice.Cells_to_be_Activated_List.list, - F1AP_Cells_to_be_Activated_List_ItemIEs_t, - cells_to_be_activated_ies); - encode_cells_to_activate(&msg->cells_to_activate[i], cells_to_be_activated_ies); + for (int i = 0; i < msg->num_cells_to_activate; i++) { + asn1cSequenceAdd(ie3->value.choice.Cells_to_be_Activated_List.list, F1AP_Cells_to_be_Activated_List_ItemIEs_t, cells); + encode_cells_to_activate(&msg->cells_to_activate[i], cells); + } } // gNB-CU RRC version (M) asn1cSequenceAdd(out->protocolIEs.list, F1AP_F1SetupResponseIEs_t, ie4); @@ -1131,7 +1138,8 @@ bool decode_f1ap_setup_response(const F1AP_F1AP_PDU_t *pdu, f1ap_setup_resp_t *o out->num_cells_to_activate = Cells_to_be_Activated_List->list.count; // Loop Cells to be Activated List Items (count >= 1) AssertError(out->num_cells_to_activate > 0, return false, "At least 1 cell must be present"); - for (int i = 0; i < out->num_cells_to_activate; i++) { + out->cells_to_activate = calloc_or_fail(out->num_cells_to_activate, sizeof(*out->cells_to_activate)); + for (int i = 0; i < out->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { const F1AP_Cells_to_be_Activated_List_ItemIEs_t *itemIEs = (F1AP_Cells_to_be_Activated_List_ItemIEs_t *)Cells_to_be_Activated_List->list.array[i]; if (!decode_cells_to_activate(&out->cells_to_activate[i], itemIEs)) @@ -1157,7 +1165,7 @@ bool eq_f1ap_setup_response(const f1ap_setup_resp_t *a, const f1ap_setup_resp_t _F1_EQ_CHECK_INT(a->num_cells_to_activate, b->num_cells_to_activate); _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); if (a->num_cells_to_activate) { - for (int i = 0; i < a->num_cells_to_activate; i++) { + for (int i = 0; i < a->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { const served_cells_to_activate_t *a_cell = &a->cells_to_activate[i]; const served_cells_to_activate_t *b_cell = &b->cells_to_activate[i]; _F1_EQ_CHECK_LONG(a_cell->nr_cellid, b_cell->nr_cellid); @@ -1197,7 +1205,8 @@ f1ap_setup_resp_t cp_f1ap_setup_response(const f1ap_setup_resp_t *msg) cp.transaction_id = msg->transaction_id; /* num_cells_available */ cp.num_cells_to_activate = msg->num_cells_to_activate; - for (int n = 0; n < msg->num_cells_to_activate; n++) { + cp.cells_to_activate = calloc_or_fail(cp.num_cells_to_activate, sizeof(*cp.cells_to_activate)); + for (int n = 0; n < msg->num_cells_to_activate && n < F1AP_MAX_NB_CELLS; n++) { /* cell.info */ served_cells_to_activate_t *cp_cell = &cp.cells_to_activate[n]; const served_cells_to_activate_t *msg_cell = &msg->cells_to_activate[n]; @@ -1230,6 +1239,7 @@ void free_f1ap_setup_response(const f1ap_setup_resp_t *msg) for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) if (msg->cells_to_activate[i].SI_msg[j].SI_container_length > 0) free(msg->cells_to_activate[i].SI_msg[j].SI_container); + free(msg->cells_to_activate); } /* ==================================== @@ -1373,6 +1383,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura /* mandatory */ /* c2. Served_Cells_To_Add */ if (msg->num_cells_to_add > 0) { + DevAssert(msg->num_cells_to_add <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie2); ie2->id = F1AP_ProtocolIE_ID_id_Served_Cells_To_Add_List; ie2->criticality = F1AP_Criticality_reject; @@ -1397,6 +1408,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura /* mandatory */ /* c3. Served_Cells_To_Modify */ if (msg->num_cells_to_modify > 0) { + DevAssert(msg->num_cells_to_modify <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie3); ie3->id = F1AP_ProtocolIE_ID_id_Served_Cells_To_Modify_List; ie3->criticality = F1AP_Criticality_reject; @@ -1426,6 +1438,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura /* mandatory */ /* c4. Served_Cells_To_Delete */ if (msg->num_cells_to_delete > 0) { + DevAssert(msg->num_cells_to_delete <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie4); ie4->id = F1AP_ProtocolIE_ID_id_Served_Cells_To_Delete_List; ie4->criticality = F1AP_Criticality_reject; @@ -1447,6 +1460,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_du_configuration_update(const f1ap_gnb_du_configura } if (msg->num_status > 0) { + DevAssert(msg->num_status <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBDUConfigurationUpdateIEs_t, ie4); ie4->id = F1AP_ProtocolIE_ID_id_Cells_Status_List; ie4->criticality = F1AP_Criticality_reject; @@ -1507,7 +1521,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du /* Served Cells To Add List */ out->num_cells_to_add = ie->value.choice.Served_Cells_To_Add_List.list.count; AssertError(out->num_cells_to_add > 0, return false, "at least 1 cell to add shall to be present"); - for (int i = 0; i < out->num_cells_to_add; i++) { + out->cell_to_add = calloc_or_fail(out->num_cells_to_add, sizeof(*out->cell_to_add)); + for (int i = 0; i < out->num_cells_to_add && i < F1AP_MAX_NB_CELLS; i++) { F1AP_Served_Cells_To_Add_Item_t *served_cells_item = &((F1AP_Served_Cells_To_Add_ItemIEs_t *)ie->value.choice.Served_Cells_To_Add_List.list.array[i]) ->value.choice.Served_Cells_To_Add_Item; @@ -1525,7 +1540,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du /* Served Cells To Modify List (O) */ out->num_cells_to_modify = ie->value.choice.Served_Cells_To_Modify_List.list.count; AssertError(out->num_cells_to_modify > 0, return false, "at least 1 cell to modify shall to be present"); - for (int i = 0; i < out->num_cells_to_modify; i++) { + out->cell_to_modify = calloc_or_fail(out->num_cells_to_modify, sizeof(*out->cell_to_modify)); + for (int i = 0; i < out->num_cells_to_modify && i < F1AP_MAX_NB_CELLS; i++) { /* Served Cells To Modify List item (count >= 1) */ F1AP_Served_Cells_To_Modify_Item_t *served_cells_item = &((F1AP_Served_Cells_To_Modify_ItemIEs_t *)ie->value.choice.Served_Cells_To_Modify_List.list.array[i]) @@ -1550,7 +1566,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du /* Served Cells To Delete List */ out->num_cells_to_delete = ie->value.choice.Served_Cells_To_Delete_List.list.count; AssertError(out->num_cells_to_delete > 0, return false, "at least 1 cell to delete shall to be present"); - for (int i = 0; i < out->num_cells_to_delete; i++) { + out->cell_to_delete = calloc_or_fail(out->num_cells_to_delete, sizeof(*out->cell_to_delete)); + for (int i = 0; i < out->num_cells_to_delete && i < F1AP_MAX_NB_CELLS; i++) { F1AP_Served_Cells_To_Delete_Item_t *served_cells_item = &((F1AP_Served_Cells_To_Delete_ItemIEs_t *)ie->value.choice.Served_Cells_To_Delete_List.list.array[i]) ->value.choice.Served_Cells_To_Delete_Item; @@ -1565,7 +1582,8 @@ bool decode_f1ap_du_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_du case F1AP_ProtocolIE_ID_id_Cells_Status_List: /* Cells Status List (O) */ out->num_status = ie->value.choice.Cells_Status_List.list.count; - for (int i = 0; i < out->num_status; ++i) { + out->status = calloc_or_fail(out->num_status, sizeof(*out->status)); + for (int i = 0; i < out->num_status && i < F1AP_MAX_NB_CELLS; ++i) { const F1AP_Cells_Status_ItemIEs_t *csi_ie = (F1AP_Cells_Status_ItemIEs_t *)ie->value.choice.Cells_Status_List.list.array[i]; AssertError(csi_ie->value.present == F1AP_Cells_Status_ItemIEs__value_PR_Cells_Status_Item, @@ -1604,11 +1622,14 @@ void free_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t for (int i = 0; i < msg->num_cells_to_add; i++) { free_f1ap_cell(&msg->cell_to_add[i].info, msg->cell_to_add[i].sys_info); } + free(msg->cell_to_add); for (int i = 0; i < msg->num_cells_to_modify; i++) { free_f1ap_cell(&msg->cell_to_modify[i].info, msg->cell_to_modify[i].sys_info); } + free(msg->cell_to_modify); + free(msg->cell_to_delete); + free(msg->status); } - /** * @brief F1 gNB-DU Configuration Update check */ @@ -1621,7 +1642,7 @@ bool eq_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t *a _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); /* to add */ _F1_EQ_CHECK_INT(a->num_cells_to_add, b->num_cells_to_add); - for (int i = 0; i < a->num_cells_to_add; i++) { + for (int i = 0; i < a->num_cells_to_add && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_cell_info(&a->cell_to_add[i].info, &b->cell_to_add[i].info)) return false; if (!eq_f1ap_sys_info(a->cell_to_add[i].sys_info, b->cell_to_add[i].sys_info)) @@ -1629,14 +1650,14 @@ bool eq_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t *a } /* to delete */ _F1_EQ_CHECK_INT(a->num_cells_to_delete, b->num_cells_to_delete); - for (int i = 0; i < a->num_cells_to_delete; i++) { + for (int i = 0; i < a->num_cells_to_delete && i < F1AP_MAX_NB_CELLS; i++) { _F1_EQ_CHECK_LONG(a->cell_to_delete[i].nr_cellid, b->cell_to_delete[i].nr_cellid); if (!eq_f1ap_plmn(&a->cell_to_delete[i].plmn, &b->cell_to_delete[i].plmn)) return false; } /* to modify */ _F1_EQ_CHECK_INT(a->num_cells_to_modify, b->num_cells_to_modify); - for (int i = 0; i < a->num_cells_to_modify; i++) { + for (int i = 0; i < a->num_cells_to_modify && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_plmn(&a->cell_to_modify[i].old_plmn, &b->cell_to_modify[i].old_plmn)) return false; _F1_EQ_CHECK_LONG(a->cell_to_modify[i].old_nr_cellid, b->cell_to_modify[i].old_nr_cellid); @@ -1647,7 +1668,7 @@ bool eq_f1ap_du_configuration_update(const f1ap_gnb_du_configuration_update_t *a } /* cell status */ _F1_EQ_CHECK_INT(a->num_status, b->num_status); - for (int i = 0; i < a->num_status; ++i) { + for (int i = 0; i < a->num_status && i < F1AP_MAX_NB_CELLS; ++i) { const f1ap_cell_status_t *astatus = &a->status[i]; const f1ap_cell_status_t *bstatus = &b->status[i]; if (!eq_f1ap_plmn(&astatus->plmn, &bstatus->plmn)) @@ -1673,19 +1694,22 @@ f1ap_gnb_du_configuration_update_t cp_f1ap_du_configuration_update(const f1ap_gn cp.transaction_id = msg->transaction_id; /* to add */ cp.num_cells_to_add = msg->num_cells_to_add; - for (int i = 0; i < cp.num_cells_to_add; ++i) { + cp.cell_to_add = calloc_or_fail(cp.num_cells_to_add, sizeof(*cp.cell_to_add)); + for (int i = 0; i < cp.num_cells_to_add && i < F1AP_MAX_NB_CELLS; ++i) { cp.cell_to_add[i].info = copy_f1ap_served_cell_info(&msg->cell_to_add[i].info); cp.cell_to_add[i].sys_info = copy_f1ap_gnb_du_system_info(msg->cell_to_add[i].sys_info); } /* to delete */ cp.num_cells_to_delete = msg->num_cells_to_delete; - for (int i = 0; i < cp.num_cells_to_delete; i++) { + cp.cell_to_delete = calloc_or_fail(cp.num_cells_to_delete, sizeof(*cp.cell_to_delete)); + for (int i = 0; i < cp.num_cells_to_delete && i < F1AP_MAX_NB_CELLS; i++) { cp.cell_to_delete[i].nr_cellid = msg->cell_to_delete[i].nr_cellid; cp.cell_to_delete[i].plmn = msg->cell_to_delete[i].plmn; } /* to modify */ cp.num_cells_to_modify = msg->num_cells_to_modify; - for (int i = 0; i < cp.num_cells_to_modify; i++) { + cp.cell_to_modify = calloc_or_fail(cp.num_cells_to_modify, sizeof(*cp.cell_to_modify)); + for (int i = 0; i < cp.num_cells_to_modify && i < F1AP_MAX_NB_CELLS; i++) { cp.cell_to_modify[i].old_plmn = msg->cell_to_modify[i].old_plmn; cp.cell_to_modify[i].old_nr_cellid = msg->cell_to_modify[i].old_nr_cellid; cp.cell_to_modify[i].info = copy_f1ap_served_cell_info(&msg->cell_to_modify[i].info); @@ -1693,7 +1717,8 @@ f1ap_gnb_du_configuration_update_t cp_f1ap_du_configuration_update(const f1ap_gn } /* cell status */ cp.num_status = msg->num_status; - for (int i = 0; i < cp.num_status; ++i) + cp.status = calloc_or_fail(cp.num_status, sizeof(*cp.status)); + for (int i = 0; i < cp.num_status && i < F1AP_MAX_NB_CELLS; ++i) cp.status[i] = msg->status[i]; return cp; } @@ -1725,15 +1750,16 @@ F1AP_F1AP_PDU_t *encode_f1ap_cu_configuration_update(const f1ap_gnb_cu_configura ieC1->value.choice.TransactionID = msg->transaction_id; /* optional c2. Cells_to_be_Activated_List (O) */ - for (int i = 0; i < msg->num_cells_to_activate; i++) { + if (msg->num_cells_to_activate > 0) { asn1cSequenceAdd(cfgUpdate->protocolIEs.list, F1AP_GNBCUConfigurationUpdateIEs_t, ieC3); ieC3->id = F1AP_ProtocolIE_ID_id_Cells_to_be_Activated_List; ieC3->criticality = F1AP_Criticality_reject; ieC3->value.present = F1AP_GNBCUConfigurationUpdateIEs__value_PR_Cells_to_be_Activated_List; - asn1cSequenceAdd(ieC3->value.choice.Cells_to_be_Activated_List.list, - F1AP_Cells_to_be_Activated_List_ItemIEs_t, - cells_to_be_activated_ies); - encode_cells_to_activate(&msg->cells_to_activate[i], cells_to_be_activated_ies); + DevAssert(msg->num_cells_to_activate <= F1AP_MAX_NB_CELLS); + for (int i = 0; i < msg->num_cells_to_activate; i++) { + asn1cSequenceAdd(ieC3->value.choice.Cells_to_be_Activated_List.list, F1AP_Cells_to_be_Activated_List_ItemIEs_t, cells); + encode_cells_to_activate(&msg->cells_to_activate[i], cells); + } } return pdu; } @@ -1768,7 +1794,8 @@ bool decode_f1ap_cu_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_cu F1AP_Cells_to_be_Activated_List_t *Cells_to_be_Activated_List = &ie->value.choice.Cells_to_be_Activated_List; out->num_cells_to_activate = Cells_to_be_Activated_List->list.count; AssertError(out->num_cells_to_activate > 0, return false, "At least 1 cell to activate must be present"); - for (int i = 0; i < out->num_cells_to_activate; i++) { + out->cells_to_activate = calloc_or_fail(out->num_cells_to_activate, sizeof(*out->cells_to_activate)); + for (int i = 0; i < out->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { if (!decode_cells_to_activate(&out->cells_to_activate[i], (F1AP_Cells_to_be_Activated_List_ItemIEs_t *)Cells_to_be_Activated_List->list.array[i])) return false; @@ -1786,8 +1813,9 @@ bool decode_f1ap_cu_configuration_update(const F1AP_F1AP_PDU_t *pdu, f1ap_gnb_cu void free_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *msg) { for (int i = 0; i < msg->num_cells_to_activate; i++) - for (int j = 0; j < msg->cells_to_activate[j].num_SI; j++) + for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) free(msg->cells_to_activate[i].SI_msg[j].SI_container); + free(msg->cells_to_activate); } /** @@ -1798,16 +1826,18 @@ bool eq_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *a _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); /* to activate */ _F1_EQ_CHECK_INT(a->num_cells_to_activate, b->num_cells_to_activate); - for (int i = 0; i < a->num_cells_to_activate; i++) { + for (int i = 0; i < a->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { _F1_EQ_CHECK_LONG(a->cells_to_activate[i].nr_cellid, b->cells_to_activate[i].nr_cellid); _F1_EQ_CHECK_INT(a->cells_to_activate[i].nrpci, b->cells_to_activate[i].nrpci); if (!eq_f1ap_plmn(&a->cells_to_activate[i].plmn, &b->cells_to_activate[i].plmn)) return false; _F1_EQ_CHECK_INT(a->cells_to_activate[i].num_SI, b->cells_to_activate[i].num_SI); for (int s = 0; s < a->cells_to_activate[i].num_SI; s++) { - _F1_EQ_CHECK_INT(*a->cells_to_activate[i].SI_msg[s].SI_container, *a->cells_to_activate[i].SI_msg[s].SI_container); - _F1_EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_container_length, a->cells_to_activate[i].SI_msg[s].SI_container_length); - _F1_EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_type, a->cells_to_activate[i].SI_msg[s].SI_type); + f1ap_sib_msg_t *a_sib_msg = &a->cells_to_activate[i].SI_msg[s]; + f1ap_sib_msg_t *b_sib_msg = &b->cells_to_activate[i].SI_msg[s]; + _F1_EQ_CHECK_INT(*a_sib_msg->SI_container, *b_sib_msg->SI_container); + _F1_EQ_CHECK_INT(a_sib_msg->SI_container_length, b_sib_msg->SI_container_length); + _F1_EQ_CHECK_INT(a_sib_msg->SI_type, b_sib_msg->SI_type); } } return true; @@ -1822,7 +1852,8 @@ f1ap_gnb_cu_configuration_update_t cp_f1ap_cu_configuration_update(const f1ap_gn /* transaction_id */ cp.transaction_id = msg->transaction_id; cp.num_cells_to_activate = msg->num_cells_to_activate; - for (int i = 0; i < cp.num_cells_to_activate; i++) { + cp.cells_to_activate = calloc_or_fail(cp.num_cells_to_activate, sizeof(*cp.cells_to_activate)); + for (int i = 0; i < cp.num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { cp.cells_to_activate[i] = msg->cells_to_activate[i]; for (int s = 0; s < cp.cells_to_activate[i].num_SI; s++) { cp.cells_to_activate[i].SI_msg[s] = msg->cells_to_activate[i].SI_msg[s]; @@ -1862,6 +1893,7 @@ F1AP_F1AP_PDU_t *encode_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_ ie1->value.choice.TransactionID = msg->transaction_id; // Cells Failed to be Activated List (0..1) if (msg->num_cells_failed_to_be_activated > 0) { + DevAssert(msg->num_cells_failed_to_be_activated <= F1AP_MAX_NB_CELLS); asn1cSequenceAdd(out->protocolIEs.list, F1AP_GNBCUConfigurationUpdateAcknowledgeIEs_t, ie2); ie2->id = F1AP_ProtocolIE_ID_id_Cells_Failed_to_be_Activated_List; ie2->criticality = F1AP_Criticality_reject; @@ -1878,9 +1910,8 @@ F1AP_F1AP_PDU_t *encode_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_ p1->cause.choice.radioNetwork = msg->cells_failed_to_be_activated[i].cause; // NR CGI (M) const plmn_id_t *plmn = &msg->cells_failed_to_be_activated[i].plmn; - MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &(p1->nRCGI.pLMN_Identity)); - printf("plmn->mcc %d %d %d %ld \n", - p1->nRCGI.pLMN_Identity.buf[0], p1->nRCGI.pLMN_Identity.buf[1], p1->nRCGI.pLMN_Identity.buf[2], p1->nRCGI.pLMN_Identity.size); + F1AP_PLMN_Identity_t *p2 = &p1->nRCGI.pLMN_Identity; + MCC_MNC_TO_PLMNID(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, p2); NR_CELL_ID_TO_BIT_STRING(msg->cells_failed_to_be_activated[i].nr_cellid, &(p1->nRCGI.nRCellIdentity)); } } @@ -1919,7 +1950,9 @@ bool decode_f1ap_cu_configuration_update_acknowledge(const F1AP_F1AP_PDU_t *pdu, F1AP_GNBCUConfigurationUpdateAcknowledgeIEs__value_PR_Cells_Failed_to_be_Activated_List); F1AP_Cells_Failed_to_be_Activated_List_t *cell_fail_list = &ie->value.choice.Cells_Failed_to_be_Activated_List; out->num_cells_failed_to_be_activated = cell_fail_list->list.count; - for (int j = 0; j < out->num_cells_failed_to_be_activated; j++) { + out->cells_failed_to_be_activated = + calloc_or_fail(out->num_cells_failed_to_be_activated, sizeof(*out->cells_failed_to_be_activated)); + for (int j = 0; j < out->num_cells_failed_to_be_activated && j < F1AP_MAX_NB_CELLS; j++) { const F1AP_Cells_Failed_to_be_Activated_List_ItemIEs_t *itemIE = (F1AP_Cells_Failed_to_be_Activated_List_ItemIEs_t *)cell_fail_list->list.array[j]; const F1AP_Cells_Failed_to_be_Activated_List_Item_t *item = &itemIE->value.choice.Cells_Failed_to_be_Activated_List_Item; @@ -1966,7 +1999,7 @@ bool eq_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); // number of cells failed to be activated _F1_EQ_CHECK_INT(a->num_cells_failed_to_be_activated, b->num_cells_failed_to_be_activated); - for (int i = 0; i < a->num_cells_failed_to_be_activated; i++) { + for (int i = 0; i < a->num_cells_failed_to_be_activated && i < F1AP_MAX_NB_CELLS; i++) { if (!eq_f1ap_plmn(&a->cells_failed_to_be_activated[i].plmn, &b->cells_failed_to_be_activated[i].plmn)) return false; _F1_EQ_CHECK_LONG(a->cells_failed_to_be_activated[i].nr_cellid, b->cells_failed_to_be_activated[i].nr_cellid); @@ -1974,21 +2007,21 @@ bool eq_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration } // TNL Associations to setup _F1_EQ_CHECK_INT(a->noofTNLAssociations_to_setup, b->noofTNLAssociations_to_setup); - for (int i = 0; i < a->noofTNLAssociations_to_setup; i++) { + for (int i = 0; i < a->noofTNLAssociations_to_setup && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) { // Explicit comparison of TNL Association fields _F1_EQ_CHECK_INT(a->tnlAssociations_to_setup[i].tl_address, b->tnlAssociations_to_setup[i].tl_address); _F1_EQ_CHECK_INT(a->tnlAssociations_to_setup[i].port, b->tnlAssociations_to_setup[i].port); } // TNL Associations failed to setup _F1_EQ_CHECK_INT(a->noofTNLAssociations_failed, b->noofTNLAssociations_failed); - for (int i = 0; i < a->noofTNLAssociations_failed; i++) { + for (int i = 0; i < a->noofTNLAssociations_failed && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) { // Explicit comparison of TNL Association fields _F1_EQ_CHECK_INT(a->tnlAssociations_failed[i].tl_address, b->tnlAssociations_failed[i].tl_address); _F1_EQ_CHECK_INT(a->tnlAssociations_failed[i].port, b->tnlAssociations_failed[i].port); } // Dedicated SI Delivery Needed UE List _F1_EQ_CHECK_INT(a->noofDedicatedSIDeliveryNeededUEs, b->noofDedicatedSIDeliveryNeededUEs); - for (int i = 0; i < a->noofDedicatedSIDeliveryNeededUEs; i++) { + for (int i = 0; i < a->noofDedicatedSIDeliveryNeededUEs && i < F1AP_MAX_NO_UE_ID; i++) { _F1_EQ_CHECK_INT(a->dedicatedSIDeliveryNeededUEs[i].gNB_CU_ue_id, b->dedicatedSIDeliveryNeededUEs[i].gNB_CU_ue_id); if (!eq_f1ap_plmn(&a->dedicatedSIDeliveryNeededUEs[i].ue_plmn, &b->dedicatedSIDeliveryNeededUEs[i].ue_plmn)) return false; @@ -2008,23 +2041,34 @@ f1ap_gnb_cu_configuration_update_acknowledge_t cp_f1ap_cu_configuration_update_a cp.transaction_id = msg->transaction_id; // number of cells failed to be activated cp.num_cells_failed_to_be_activated = msg->num_cells_failed_to_be_activated; - for (int i = 0; i < cp.num_cells_failed_to_be_activated; i++) + cp.cells_failed_to_be_activated = calloc_or_fail(cp.num_cells_failed_to_be_activated, sizeof(*cp.cells_failed_to_be_activated)); + for (int i = 0; i < cp.num_cells_failed_to_be_activated && i < F1AP_MAX_NB_CELLS; i++) cp.cells_failed_to_be_activated[i] = msg->cells_failed_to_be_activated[i]; // TNL Associations to setup cp.noofTNLAssociations_to_setup = msg->noofTNLAssociations_to_setup; - for (int i = 0; i < cp.noofTNLAssociations_to_setup; i++) + for (int i = 0; i < cp.noofTNLAssociations_to_setup && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) cp.tnlAssociations_to_setup[i] = msg->tnlAssociations_to_setup[i]; // TNL Associations failed to setup cp.noofTNLAssociations_failed = msg->noofTNLAssociations_failed; - for (int i = 0; i < cp.noofTNLAssociations_failed; i++) + for (int i = 0; i < cp.noofTNLAssociations_failed && i < F1AP_MAX_NO_OF_TNL_ASSOCIATIONS; i++) cp.tnlAssociations_failed[i] = msg->tnlAssociations_failed[i]; // Dedicated SI Delivery Needed UE List cp.noofDedicatedSIDeliveryNeededUEs = msg->noofDedicatedSIDeliveryNeededUEs; - for (int i = 0; i < cp.noofDedicatedSIDeliveryNeededUEs; i++) + cp.dedicatedSIDeliveryNeededUEs = calloc_or_fail(cp.noofDedicatedSIDeliveryNeededUEs, sizeof(*cp.dedicatedSIDeliveryNeededUEs)); + for (int i = 0; i < cp.noofDedicatedSIDeliveryNeededUEs && i < F1AP_MAX_NO_UE_ID; i++) cp.dedicatedSIDeliveryNeededUEs[i] = msg->dedicatedSIDeliveryNeededUEs[i]; return cp; } +/** + * @brief F1 gNB-CU Configuration Update Acknowledge memory management + */ +void free_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration_update_acknowledge_t *msg) +{ + free(msg->cells_failed_to_be_activated); + free(msg->dedicatedSIDeliveryNeededUEs); +} + /* ================================== * F1AP gNB-DU Configuration Ack * ================================== */ @@ -2094,10 +2138,11 @@ bool decode_f1ap_du_configuration_update_acknowledge(const F1AP_F1AP_PDU_t *pdu, _F1_EQ_CHECK_INT(ie->value.present, F1AP_GNBDUConfigurationUpdateAcknowledgeIEs__value_PR_Cells_to_be_Activated_List); F1AP_Cells_to_be_Activated_List_t *list = &ie->value.choice.Cells_to_be_Activated_List; out->num_cells_to_activate = list->list.count; - for (int j = 0; j < out->num_cells_to_activate; j++) { + out->cells_to_activate = calloc_or_fail(out->num_cells_to_activate, sizeof(*out->cells_to_activate)); + for (int j = 0; j < out->num_cells_to_activate && j < F1AP_MAX_NB_CELLS; j++) { const F1AP_Cells_to_be_Activated_List_ItemIEs_t *itemIE = (F1AP_Cells_to_be_Activated_List_ItemIEs_t *)list->list.array[j]; - decode_cells_to_activate(&out->cells_to_activate[0], itemIE); + decode_cells_to_activate(&out->cells_to_activate[j], itemIE); } break; } @@ -2120,7 +2165,7 @@ bool eq_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration // number of cells to activate _F1_EQ_CHECK_INT(a->num_cells_to_activate, b->num_cells_to_activate); // loop over cells to activate - for (int i = 0; i < a->num_cells_to_activate; i++) { + for (int i = 0; i < a->num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { _F1_EQ_CHECK_LONG(a->cells_to_activate[i].nr_cellid, b->cells_to_activate[i].nr_cellid); _F1_EQ_CHECK_INT(a->cells_to_activate[i].nrpci, b->cells_to_activate[i].nrpci); if (!eq_f1ap_plmn(&a->cells_to_activate[i].plmn, &b->cells_to_activate[i].plmn)) @@ -2148,7 +2193,8 @@ f1ap_gnb_du_configuration_update_acknowledge_t cp_f1ap_du_configuration_update_a // number of cells to activate cp.num_cells_to_activate = msg->num_cells_to_activate; // Loop through cells to activate - for (int i = 0; i < cp.num_cells_to_activate; i++) { + cp.cells_to_activate = calloc_or_fail(cp.num_cells_to_activate, sizeof(*cp.cells_to_activate)); + for (int i = 0; i < cp.num_cells_to_activate && i < F1AP_MAX_NB_CELLS; i++) { cp.cells_to_activate[i] = msg->cells_to_activate[i]; for (int s = 0; s < cp.cells_to_activate[i].num_SI; s++) { f1ap_sib_msg_t *cp_sib = &cp.cells_to_activate[i].SI_msg[s]; @@ -2175,4 +2221,5 @@ void free_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configurati } } } + free(msg->cells_to_activate); } diff --git a/openair2/F1AP/lib/f1ap_interface_management.h b/openair2/F1AP/lib/f1ap_interface_management.h index 1b484f3476..942a9932ad 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.h +++ b/openair2/F1AP/lib/f1ap_interface_management.h @@ -46,8 +46,6 @@ bool decode_f1ap_setup_request(const struct F1AP_F1AP_PDU *pdu, f1ap_setup_req_t f1ap_setup_req_t cp_f1ap_setup_request(const f1ap_setup_req_t *msg); bool eq_f1ap_setup_request(const f1ap_setup_req_t *a, const f1ap_setup_req_t *b); void free_f1ap_setup_request(const f1ap_setup_req_t *msg); -f1ap_served_cell_info_t copy_f1ap_served_cell_info(const f1ap_served_cell_info_t *src); -void free_f1ap_cell(const f1ap_served_cell_info_t *info, const f1ap_gnb_du_system_info_t *sys_info); /* F1 Setup Response */ struct F1AP_F1AP_PDU *encode_f1ap_setup_response(const f1ap_setup_resp_t *msg); @@ -84,6 +82,7 @@ bool eq_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration const f1ap_gnb_cu_configuration_update_acknowledge_t *b); f1ap_gnb_cu_configuration_update_acknowledge_t cp_f1ap_cu_configuration_update_acknowledge( const f1ap_gnb_cu_configuration_update_acknowledge_t *msg); +void free_f1ap_cu_configuration_update_acknowledge(const f1ap_gnb_cu_configuration_update_acknowledge_t *msg); /* F1 gNB-DU Configuration Update Acknowledge */ struct F1AP_F1AP_PDU *encode_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration_update_acknowledge_t *msg); diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index cba787f4dc..e60d768b94 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -244,8 +244,9 @@ static void test_f1ap_setup_request(void) .rrc_ver[0] = 12, .rrc_ver[1] = 34, .rrc_ver[2] = 56, - .cell[0].info = info, }; + orig.cell = calloc_or_fail(orig.num_cells_available, sizeof(*orig.cell)); + orig.cell[0].info = info; orig.cell[0].sys_info = calloc_or_fail(1, sizeof(*orig.cell[0].sys_info)); *orig.cell[0].sys_info = sys_info; // encode @@ -322,12 +323,14 @@ static void test_f1ap_setup_response(void) .rrc_ver[1] = 34, .rrc_ver[2] = 56, .num_cells_to_activate = 1, - .cells_to_activate[0].nr_cellid = 123456, - .cells_to_activate[0].nrpci = 1, - .cells_to_activate[0].plmn.mcc = 12, - .cells_to_activate[0].plmn.mnc = 123, - .cells_to_activate[0].plmn.mnc_digit_length = 3, }; + /* Allocate memory for cells_to_activate array */ + orig.cells_to_activate = calloc_or_fail(orig.num_cells_to_activate, sizeof(*orig.cells_to_activate)); + orig.cells_to_activate[0].nr_cellid = 123456; + orig.cells_to_activate[0].nrpci = 1; + orig.cells_to_activate[0].plmn.mcc = 12; + orig.cells_to_activate[0].plmn.mnc = 123; + orig.cells_to_activate[0].plmn.mnc_digit_length = 3; /* Cells to activate */ if (orig.num_cells_to_activate) { /* SI_container */ @@ -556,30 +559,37 @@ static void test_f1ap_du_configuration_update(void) f1ap_gnb_du_configuration_update_t orig = { .transaction_id = 2, .num_cells_to_add = 1, - .cell_to_add[0].info = info2, .num_cells_to_modify = 1, - .cell_to_modify[0].info = info, - .cell_to_modify[0].old_nr_cellid = 1235UL, - .cell_to_modify[0].old_plmn.mcc = 208, - .cell_to_modify[0].old_plmn.mnc = 88, - .cell_to_modify[0].old_plmn.mnc_digit_length = 2, .num_cells_to_delete = 1, - .cell_to_delete[0].nr_cellid = 1234UL, - .cell_to_delete[0].plmn.mcc = 1, - .cell_to_delete[0].plmn.mnc = 1, - .cell_to_delete[0].plmn.mnc_digit_length = 3, .num_status = 2, - .status[0].nr_cellid = 542UL, - .status[0].plmn.mcc = 1, - .status[0].plmn.mnc = 2, - .status[0].plmn.mnc_digit_length = 3, - .status[0].service_state = F1AP_STATE_IN_SERVICE, - .status[1].nr_cellid = 33UL, - .status[1].plmn.mcc = 5, - .status[1].plmn.mnc = 13, - .status[1].plmn.mnc_digit_length = 2, - .status[1].service_state = F1AP_STATE_OUT_OF_SERVICE, }; + /* Allocate memory for arrays */ + orig.cell_to_add = calloc_or_fail(orig.num_cells_to_add, sizeof(*orig.cell_to_add)); + orig.cell_to_modify = calloc_or_fail(orig.num_cells_to_modify, sizeof(*orig.cell_to_modify)); + orig.cell_to_delete = calloc_or_fail(orig.num_cells_to_delete, sizeof(*orig.cell_to_delete)); + orig.status = calloc_or_fail(orig.num_status, sizeof(*orig.status)); + + /* Fill in the data */ + orig.cell_to_add[0].info = info2; + orig.cell_to_modify[0].info = info; + orig.cell_to_modify[0].old_nr_cellid = 1235UL; + orig.cell_to_modify[0].old_plmn.mcc = 208; + orig.cell_to_modify[0].old_plmn.mnc = 88; + orig.cell_to_modify[0].old_plmn.mnc_digit_length = 2; + orig.cell_to_delete[0].nr_cellid = 1234UL; + orig.cell_to_delete[0].plmn.mcc = 1; + orig.cell_to_delete[0].plmn.mnc = 1; + orig.cell_to_delete[0].plmn.mnc_digit_length = 3; + orig.status[0].nr_cellid = 542UL; + orig.status[0].plmn.mcc = 1; + orig.status[0].plmn.mnc = 2; + orig.status[0].plmn.mnc_digit_length = 3; + orig.status[0].service_state = F1AP_STATE_IN_SERVICE; + orig.status[1].nr_cellid = 33UL; + orig.status[1].plmn.mcc = 5; + orig.status[1].plmn.mnc = 13; + orig.status[1].plmn.mnc_digit_length = 2; + orig.status[1].service_state = F1AP_STATE_OUT_OF_SERVICE; orig.cell_to_modify[0].sys_info = calloc_or_fail(1, sizeof(*orig.cell_to_modify[0].sys_info)); *orig.cell_to_modify[0].sys_info = sys_info; orig.gNB_DU_ID = malloc_or_fail(sizeof(*orig.gNB_DU_ID)); @@ -614,18 +624,18 @@ static void test_f1ap_du_configuration_update_acknowledge(void) f1ap_gnb_du_configuration_update_acknowledge_t orig = { .transaction_id = 5, .num_cells_to_activate = 1, - .cells_to_activate = {{ - .nr_cellid = 987654321, - .nrpci = 50, - .plmn = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2}, - .num_SI = 1, - .SI_msg = {{ - .SI_type = 7, - .SI_container_length = 10, - .SI_container = malloc(sizeof(uint8_t) * 10), - }}, - }}, }; + // Allocate memory for cells_to_activate array + orig.cells_to_activate = calloc_or_fail(orig.num_cells_to_activate, sizeof(*orig.cells_to_activate)); + orig.cells_to_activate[0].nr_cellid = 987654321; + orig.cells_to_activate[0].nrpci = 50; + orig.cells_to_activate[0].plmn.mcc = 001; + orig.cells_to_activate[0].plmn.mnc = 01; + orig.cells_to_activate[0].plmn.mnc_digit_length = 2; + orig.cells_to_activate[0].num_SI = 1; + orig.cells_to_activate[0].SI_msg[0].SI_type = 7; + orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10; + orig.cells_to_activate[0].SI_msg[0].SI_container = malloc(sizeof(uint8_t) * 10); for (int i = 0; i < orig.cells_to_activate[0].SI_msg[0].SI_container_length; i++) { orig.cells_to_activate[0].SI_msg[0].SI_container[i] = i; } @@ -657,17 +667,21 @@ static void test_f1ap_du_configuration_update_acknowledge(void) static void test_f1ap_cu_configuration_update(void) { /* create message */ - f1ap_gnb_cu_configuration_update_t orig = {.transaction_id = 2, - .num_cells_to_activate = 1, - .cells_to_activate = {{.nr_cellid = 123456789, - .nrpci = 100, - .plmn = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2}, - .num_SI = 1, - .SI_msg = {{ - .SI_type = 7, - .SI_container_length = 10, - .SI_container = malloc(sizeof(uint8_t) * 10), - }}}}}; + f1ap_gnb_cu_configuration_update_t orig = { + .transaction_id = 2, + .num_cells_to_activate = 1, + }; + // Allocate memory for cells_to_activate array + orig.cells_to_activate = calloc_or_fail(orig.num_cells_to_activate, sizeof(*orig.cells_to_activate)); + orig.cells_to_activate[0].nr_cellid = 123456789; + orig.cells_to_activate[0].nrpci = 100; + orig.cells_to_activate[0].plmn.mcc = 001; + orig.cells_to_activate[0].plmn.mnc = 01; + orig.cells_to_activate[0].plmn.mnc_digit_length = 2; + orig.cells_to_activate[0].num_SI = 1; + orig.cells_to_activate[0].SI_msg[0].SI_type = 7; + orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10; + orig.cells_to_activate[0].SI_msg[0].SI_container = malloc(sizeof(uint8_t) * 10); F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update(&orig); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); f1ap_msg_free(f1enc); @@ -697,10 +711,14 @@ static void test_f1ap_cu_configuration_update_acknowledge(void) f1ap_gnb_cu_configuration_update_acknowledge_t orig = { .transaction_id = 2, .num_cells_failed_to_be_activated = 1, - .cells_failed_to_be_activated = {{.nr_cellid = 123456789, - .plmn = {.mcc = 001, .mnc = 01, .mnc_digit_length = 2}, - .cause = F1AP_CAUSE_RADIO_NETWORK}} - }; + }; + // Allocate memory for cells_failed_to_be_activated array + orig.cells_failed_to_be_activated = calloc_or_fail(orig.num_cells_failed_to_be_activated, sizeof(*orig.cells_failed_to_be_activated)); + orig.cells_failed_to_be_activated[0].nr_cellid = 123456789; + orig.cells_failed_to_be_activated[0].plmn.mcc = 001; + orig.cells_failed_to_be_activated[0].plmn.mnc = 01; + orig.cells_failed_to_be_activated[0].plmn.mnc_digit_length = 2; + orig.cells_failed_to_be_activated[0].cause = F1AP_CAUSE_RADIO_NETWORK; // F1AP Enc/dec F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update_acknowledge(&orig); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); @@ -713,13 +731,14 @@ static void test_f1ap_cu_configuration_update_acknowledge(void) // Equality check ret = eq_f1ap_cu_configuration_update_acknowledge(&orig, &decoded); AssertFatal(ret, "eq_f1ap_cu_configuration_update_acknowledge(): decoded message doesn't match\n"); - // No free needed + free_f1ap_cu_configuration_update_acknowledge(&decoded); // Deep copy f1ap_gnb_cu_configuration_update_acknowledge_t cp = cp_f1ap_cu_configuration_update_acknowledge(&orig); // Equality check ret = eq_f1ap_cu_configuration_update_acknowledge(&orig, &cp); AssertFatal(ret, "eq_f1ap_cu_configuration_update_acknowledge(): copied message doesn't match\n"); - // No free needed + free_f1ap_cu_configuration_update_acknowledge(&cp); + free_f1ap_cu_configuration_update_acknowledge(&orig); } static byte_array_t get_test_ba(const char *s) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 01c44650ae..3a8f580471 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -75,6 +75,7 @@ #include "s1ap_messages_types.h" #include "sctp_default_values.h" #include "seq_arr.h" +#include "common/utils/alg/find.h" #include "uper_encoder.h" #include "utils.h" #include "x2ap_messages_types.h" @@ -1160,6 +1161,7 @@ static f1ap_setup_req_t *RC_read_F1Setup(uint64_t id, req->gNB_DU_id = id; req->gNB_DU_name = strdup(name); req->num_cells_available = 1; + req->cell = calloc_or_fail(req->num_cells_available, sizeof(*req->cell)); req->cell[0].info = *info; LOG_I(GNB_APP, "F1AP: gNB idx %d gNB_DU_id %ld, gNB_DU_name %s, TAC %d MCC/MNC/length %d/%d/%d cellID %ld\n", @@ -1681,6 +1683,7 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) seq_arr_t *du_SIBs = RC.nrmac[0]->common_channels[0].du_SIBs; f1ap_setup_req_t *req = RC_read_F1Setup(gnb_du_id, name, &info, scc, mib, sib1, du_SIBs); AssertFatal(req != NULL, "could not read F1 Setup information\n"); + LOG_I(GNB_APP, "Configured DU: cell ID %lu, PCI %d\n", info.nr_cellid, info.nr_pci); RC.nrmac[0]->f1_config.setup_req = req; RC.nrmac[0]->f1_config.gnb_id = gnb_id; @@ -1849,6 +1852,22 @@ static seq_arr_t *fill_cu_sibs(paramdef_t *GNBparamarray) return SIBs; } +static bool eq_neighbour_pci(const void *vval, const void *vit) +{ + const int *pci = (const int *)vval; + const nr_neighbour_cell_t *neighbour = (const nr_neighbour_cell_t *)vit; + return neighbour->physicalCellId == *pci; +} + +/** @brief Find neighbour by PCI within a cell's neighbour list (first match) */ +static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int pci) +{ + elm_arr_t elm = find_if(neighbour_cells, &pci, eq_neighbour_pci); + if (elm.found) + return (nr_neighbour_cell_t *)elm.it; + return NULL; +} + static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc) { char gnbpath[MAX_OPTNAME_SIZE + 8]; @@ -1906,6 +1925,14 @@ static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc n.plmn.mcc = *NeighbourPlmn[GNB_MOBILE_COUNTRY_CODE_IDX].uptr; n.plmn.mnc = *NeighbourPlmn[GNB_MOBILE_NETWORK_CODE_IDX].uptr; n.plmn.mnc_digit_length = *NeighbourPlmn[GNB_MNC_DIGIT_LENGTH].uptr; + if (get_neighbour_by_pci(cell.neighbour_cells, n.physicalCellId) != NULL) { + LOG_E(GNB_APP, + "Cell %ld: duplicate PCI %d in neighbour list (nrcell_id %ld)\n", + cell.nr_cell_id, + n.physicalCellId, + n.nrcell_id); + AssertFatal(false, "PCI must be unique within a cell's neighbour list\n"); + } seq_arr_push_back(cell.neighbour_cells, &n, sizeof(n)); LOG_I(GNB_APP, " [%d] neighbor ID %d cellId %ld PCI %d SCS %d SSB ARFCN %u TAC %u PLMN %03u.%0*u\n", @@ -2050,8 +2077,6 @@ gNB_RRC_INST *RCconfig_NRRRC() rrc->eth_params_s.transp_preference = ETH_UDP_MODE; } - rrc->nr_cellid = (uint64_t)*(GNBParamList.paramarray[i][GNB_NRCELLID_IDX].u64ptr); - if (strcmp(*(GNBParamList.paramarray[i][GNB_TRANSPORT_S_PREFERENCE_IDX].strptr), "local_mac") == 0) { } else if (strcmp(*(GNBParamList.paramarray[i][GNB_TRANSPORT_S_PREFERENCE_IDX].strptr), "cudu") == 0) { diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index dfa68773a4..ca1a516c0f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1093,14 +1093,17 @@ void prepare_du_configuration_update(gNB_MAC_INST *mac, { /* send gNB-DU configuration update to RRC */ f1ap_gnb_du_configuration_update_t update = { - .transaction_id = 1, - .num_status = 1, - .status[0].plmn = info->plmn, - .status[0].nr_cellid = info->nr_cellid, - .status[0].service_state = F1AP_STATE_IN_SERVICE, + .transaction_id = 1, + .num_status = 1, + .status = calloc_or_fail(1, sizeof(f1ap_cell_status_t)), }; + update.status[0].plmn = info->plmn; + update.status[0].nr_cellid = info->nr_cellid; + update.status[0].service_state = F1AP_STATE_IN_SERVICE; + if (mib && sib1) { - update.num_cells_to_modify = 1, + update.num_cells_to_modify = 1; + update.cell_to_modify = calloc_or_fail(1, sizeof(*update.cell_to_modify)); update.cell_to_modify[0].old_nr_cellid = info->nr_cellid; update.cell_to_modify[0].info = *info; update.cell_to_modify[0].sys_info = get_sys_info(mib, sib1, NULL); diff --git a/openair2/RRC/NR/CMakeLists.txt b/openair2/RRC/NR/CMakeLists.txt index 658bfeee5a..9ca17a42bd 100644 --- a/openair2/RRC/NR/CMakeLists.txt +++ b/openair2/RRC/NR/CMakeLists.txt @@ -1,5 +1,10 @@ add_subdirectory(MESSAGES) +# Library for RRC cell management functions +add_library(rrc_cell_management STATIC rrc_cell_management.c) +target_link_libraries(rrc_cell_management PUBLIC asn1_nr_rrc asn1_lte_rrc) +target_link_libraries(rrc_cell_management PRIVATE utils LOG asn1_nr_rrc_hdrs asn1_lte_rrc_hdrs) + if(ENABLE_TESTS) add_subdirectory(tests) endif() diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 08e0d27888..0379aaddcd 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -171,6 +171,21 @@ typedef struct { uint64_t ul_br; } nr_rrc_ambr_t; +/** @brief UE serving cell information + * @note ServCellIndex is a short identity used to uniquely identify a serving cell + * (PCell, PSCell, or SCell) across cell groups (TS 38.331). + * Value 0 applies for the PCell, while the SCellIndex that has previously + * been assigned applies for SCells. + * @note Range: 0..maxNrofServingCells-1 where maxNrofServingCells = 32 */ +typedef struct { + /* NR Cell Identity (cell_id) */ + uint64_t nci; + /* ServCellIndex (TS 38.331): 0 = PCell, 1-31 = SCell */ + uint8_t serving_cell_id; + /* SCTP association ID of the DU that owns this cell (for fast lookup) */ + sctp_assoc_t assoc_id; +} ue_serving_cell_t; + /* forward declaration */ typedef struct nr_handover_context_s nr_handover_context_t; @@ -212,7 +227,9 @@ typedef struct gNB_RRC_UE_s { uint64_t ng_5G_S_TMSI_Part1; NR_EstablishmentCause_t establishment_cause; - uint64_t nr_cellid; + /* Dynamic array of UE serving cells */ + seq_arr_t serving_cells; /* ue_serving_cell_t */ + uint32_t rrc_ue_id; uint64_t amf_ue_ngap_id; // Globally Unique AMF Identifier @@ -343,15 +360,64 @@ typedef struct cucp_cuup_if_s { cucp_cuup_bearer_context_release_func_t bearer_context_release; } cucp_cuup_if_t; +typedef struct { + int band; + uint32_t arfcn; + uint8_t scs; + uint16_t nrb; +} nr_rrc_freq_info_t; + +typedef struct { + nr_rrc_freq_info_t dlul; +} nr_rrc_tdd_info_t; + +typedef struct { + nr_rrc_freq_info_t dl; + nr_rrc_freq_info_t ul; +} nr_rrc_fdd_info_t; + +typedef struct { + /* operating mode: TDD or FDD */ + enum { NR_MODE_TDD = 0, NR_MODE_FDD = 1 } mode; + uint64_t cell_id; + uint16_t pci; + union { + nr_rrc_tdd_info_t tdd; + nr_rrc_fdd_info_t fdd; + }; + plmn_id_t plmn; + uint16_t tac; +} nr_rrc_cell_info_t; + +typedef struct nr_rrc_cell_container_t { + /* Tree-related data */ + RB_ENTRY(nr_rrc_cell_container_t) entries; + /* transport association */ + sctp_assoc_t assoc_id; + /* Cell-only RRC-local info */ + nr_rrc_cell_info_t info; + /* MIB message (6.2.2 TS 38.331) */ + NR_MIB_t *mib; + /* SIB1 message (6.2.2 TS 38.331) */ + NR_SIB1_t *sib1; + /* MeasurementTimingConfiguration inter-node (TS 38.331) */ + NR_MeasurementTimingConfiguration_t *mtc; +} nr_rrc_cell_container_t; + typedef struct nr_rrc_du_container_t { /* Tree-related data */ RB_ENTRY(nr_rrc_du_container_t) entries; - + /* DU-only information */ + /* Transport association identifier for this DU */ sctp_assoc_t assoc_id; - f1ap_setup_req_t *setup_req; - NR_MIB_t *mib; - NR_SIB1_t *sib1; - NR_MeasurementTimingConfiguration_t *mtc; + /* DU identity */ + uint64_t gNB_DU_id; + /* DU name */ + char *gNB_DU_name; + /* RRC version */ + uint8_t rrc_ver[3]; + /* Cells, indexed by cell_id */ + seq_arr_t cells; /* nr_rrc_cell_container_t* */ } nr_rrc_du_container_t; typedef struct nr_rrc_cuup_container_t { @@ -372,8 +438,6 @@ typedef struct gNB_RRC_INST_s { eth_params_t eth_params_s; uid_allocator_t uid_allocator; RB_HEAD(rrc_nr_ue_tree_s, rrc_gNB_ue_context_s) rrc_ue_head; // ue_context tree key search by rnti - /// NR cell id - uint64_t nr_cellid; // RRC configuration gNB_RrcConfigurationReq configuration; @@ -395,6 +459,10 @@ typedef struct gNB_RRC_INST_s { RB_HEAD(rrc_du_tree, nr_rrc_du_container_t) dus; // DUs, indexed by assoc_id size_t num_dus; + /* Global cell tree, indexed by cell_id */ + RB_HEAD(rrc_cell_tree, nr_rrc_cell_container_t) cells; + size_t num_cells; + RB_HEAD(rrc_cuup_tree, nr_rrc_cuup_container_t) cuups; // CU-UPs, indexed by assoc_id size_t num_cuups; @@ -403,8 +471,12 @@ typedef struct gNB_RRC_INST_s { nr_rlc_configuration_t rlc_config; } gNB_RRC_INST; +/** Forward declaration for UE log macros */ +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue); + #define UE_LOG_FMT "(cellID %lx, UE ID %d RNTI %04x)" -#define UE_LOG_ARGS(ue_context) (ue_context)->nr_cellid, (ue_context)->rrc_ue_id, (ue_context)->rnti +#define UE_LOG_ARGS(ue_context) \ + (ue_get_pcell_entry(ue_context) ? ue_get_pcell_entry(ue_context)->nci : 0), (ue_context)->rrc_ue_id, (ue_context)->rnti #define LOG_UE_DL_EVENT(ue_context, fmt, ...) LOG_A(NR_RRC, "[DL] " UE_LOG_FMT " " fmt, UE_LOG_ARGS(ue_context) __VA_OPT__(,) __VA_ARGS__) #define LOG_UE_EVENT(ue_context, fmt, ...) LOG_A(NR_RRC, "[--] " UE_LOG_FMT " " fmt, UE_LOG_ARGS(ue_context) __VA_OPT__(,) __VA_ARGS__) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 28fffd2cda..a01c9aade6 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -128,11 +128,11 @@ 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 cell Target cell container containing cell 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 nr_rrc_cell_container_t *cell, const byte_array_t *ho_prep_info); #endif diff --git a/openair2/RRC/NR/rrc_cell_management.c b/openair2/RRC/NR/rrc_cell_management.c new file mode 100644 index 0000000000..0df2665c04 --- /dev/null +++ b/openair2/RRC/NR/rrc_cell_management.c @@ -0,0 +1,411 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include "rrc_cell_management.h" +#include "common/utils/assertions.h" // for DevAssert +#include "common/utils/collection/tree.h" // for RB_FOREACH +#include "common/utils/alg/find.h" // for find_if +#include "common/utils/ds/seq_arr.h" // for seq_arr_t, seq_arr_free +#include "common/utils/LOG/log.h" // for LOG_I, LOG_W, LOG_E +#include "openair2/F1AP/f1ap_ids.h" // for cu_exists_f1_ue_data, cu_get_f1_ue_data +#include "asn_application.h" // for ASN_STRUCT_FREE +#include "NR_MIB.h" // for asn_DEF_NR_MIB +#include "NR_SIB1.h" // for asn_DEF_NR_SIB1 +#include "NR_MeasurementTimingConfiguration.h" // for asn_DEF_NR_MeasurementTimingConfiguration + +/** @brief Comparison function for DU tree + * @param[in] a Pointer to first DU container + * @param[in] b Pointer to second DU container + * @return Comparison result */ +int du_compare(const nr_rrc_du_container_t *a, const nr_rrc_du_container_t *b) +{ + if (a->assoc_id > b->assoc_id) + return 1; + if (a->assoc_id == b->assoc_id) + return 0; + return -1; /* a->assoc_id < b->assoc_id */ +} + +// Generate RB tree functions for DU tree +RB_GENERATE(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); + +/** @brief Find DU by SCTP association ID (uses DU RB-tree lookup) + * @param[in] rrc Pointer to RRC instance + * @param[in] assoc_id SCTP association ID + * @return Pointer to the DU container, or NULL if not found + * @note O(log N_DU) complexity using DU RB-tree lookup */ +nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, const sctp_assoc_t assoc_id) +{ + DevAssert(rrc != NULL); + nr_rrc_du_container_t e = {.assoc_id = assoc_id}; + return RB_FIND(rrc_du_tree, &rrc->dus, &e); +} + +/** @brief Add DU to RRC DU tree and increment rrc->num_dus on success + * @param[in] rrc Pointer to RRC instance + * @param[in] du Pointer to DU container to add + * @return NULL if inserted successfully; pointer to existing DU (collision) if duplicate assoc_id */ +nr_rrc_du_container_t *rrc_add_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + DevAssert(rrc != NULL); + DevAssert(du != NULL); + nr_rrc_du_container_t *collision = RB_INSERT(rrc_du_tree, &rrc->dus, du); + if (collision == NULL) + rrc->num_dus++; + return collision; +} + +/** @brief Free a DU container and its allocated fields (gNB_DU_name); free the DU itself + * @param[in] du Pointer to DU container to free + * @note Does NOT remove the DU from the tree or free du->cells; caller must do RB_REMOVE and seq_arr_free first */ +void rrc_free_du_container(nr_rrc_du_container_t *du) +{ + DevAssert(du != NULL); + free(du->gNB_DU_name); + free(du); +} + +/** @brief Remove DU from RRC DU tree, decrement rrc->num_dus, and free the DU and its resources + * @param[in] rrc Pointer to RRC instance + * @param[in] du Pointer to DU container to remove (caller must have removed and freed all cells first) */ +void rrc_rm_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + DevAssert(rrc != NULL); + DevAssert(du != NULL); + nr_rrc_du_container_t *removed = RB_REMOVE(rrc_du_tree, &rrc->dus, du); + AssertFatal(removed == du, "Failed to remove DU (assoc_id %d) from tree\n", du->assoc_id); + rrc->num_dus--; + seq_arr_free(&du->cells, NULL); + rrc_free_du_container(du); +} + +/** @brief Remove all cells from the DU (and from global cell tree), remove the DU from the tree, and free the DU + * @param[in] rrc Pointer to RRC instance + * @param[in] du Pointer to DU container to cleanup (removed from tree and freed) */ +void rrc_cleanup_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + DevAssert(rrc != NULL); + DevAssert(du != NULL); + for (size_t i = seq_arr_size(&du->cells); i > 0; i--) { + nr_rrc_cell_container_t **cell_ptr = seq_arr_at(&du->cells, i - 1); + nr_rrc_cell_container_t *cell = *cell_ptr; + LOG_I(NR_RRC, "Removing cell %lu from DU cells array\n", cell->info.cell_id); + seq_arr_erase(&du->cells, cell_ptr); + rrc_rm_cell(rrc, cell); + } + rrc_rm_du(rrc, du); +} + +/** @brief Comparison function for cell tree + * @param[in] a Pointer to first cell container + * @param[in] b Pointer to second cell container + * @return Comparison result */ +int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b) +{ + if (a->info.cell_id < b->info.cell_id) + return -1; + if (a->info.cell_id > b->info.cell_id) + return 1; + return 0; +} + +/** @brief Generate RB tree functions for cell tree */ +RB_GENERATE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); + +/** @brief Find cell container by cell ID (lookup in global cell tree) + * @param[in] cells Pointer to global cell tree + * @param[in] cell_id Cell ID + * @return Pointer to the cell container, or NULL if not found + * @note O(log N_CELL) using RB-tree lookup */ +nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const uint64_t cell_id) +{ + DevAssert(cells != NULL); + nr_rrc_cell_container_t search_key = {.info = {.cell_id = cell_id}}; + return RB_FIND(rrc_cell_tree, cells, &search_key); +} + +/** @brief Add cell to global cell tree and increment rrc->num_cells on success + * @param[in] rrc Pointer to RRC instance + * @param[in] cell Pointer to cell container to add + * @return NULL if inserted successfully; pointer to existing cell (collision) if duplicate cell_id */ +nr_rrc_cell_container_t *rrc_add_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell) +{ + DevAssert(rrc != NULL); + DevAssert(cell != NULL); + nr_rrc_cell_container_t *collision = RB_INSERT(rrc_cell_tree, &rrc->cells, cell); + if (collision == NULL) + rrc->num_cells++; + return collision; +} + +/** @brief Remove cell from global cell tree, decrement rrc->num_cells, and free the cell + * @param[in] rrc Pointer to RRC instance + * @param[in] cell Pointer to cell container to remove */ +void rrc_rm_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell) +{ + DevAssert(rrc != NULL); + DevAssert(cell != NULL); + nr_rrc_cell_container_t *removed = RB_REMOVE(rrc_cell_tree, &rrc->cells, cell); + AssertFatal(removed == cell, "Failed to remove cell %ld from tree\n", cell->info.cell_id); + rrc->num_cells--; + rrc_free_cell_container(cell); +} + +static bool eq_cell_id(const void *vval, const void *vit) +{ + const uint64_t *id = (const uint64_t *)vval; + const nr_rrc_cell_container_t **elem_ptr = (const nr_rrc_cell_container_t **)vit; + return (*elem_ptr) != NULL && (*elem_ptr)->info.cell_id == *id; +} + +/** @brief Get cell by cell_id from DU's cells array + * @param[in] cells Pointer to DU's cells sequence array + * @param[in] cell_id Cell ID to search for + * @return Pointer to the cell container, or NULL if not found */ +nr_rrc_cell_container_t *rrc_get_cell_for_du(seq_arr_t *cells, uint64_t cell_id) +{ + DevAssert(cells); + elm_arr_t elm = find_if(cells, &cell_id, eq_cell_id); + if (elm.found) { + // elm.it points to an element in seq_arr, which stores nr_rrc_cell_container_t* + nr_rrc_cell_container_t **cell_ptr = (nr_rrc_cell_container_t **)elm.it; + return *cell_ptr; + } + return NULL; +} + +static bool eq_pci_in_du(const void *vval, const void *vit) +{ + const uint16_t *pci = (const uint16_t *)vval; + const nr_rrc_cell_container_t **elem_ptr = (const nr_rrc_cell_container_t **)vit; + return (*elem_ptr) != NULL && (*elem_ptr)->info.pci == *pci; +} + +/** @brief Get cell by PCI from DU's cells array + * @param[in] cells Pointer to DU's cells sequence array + * @param[in] pci PCI to search for + * @return Pointer to the cell container, or NULL if not found + * @note PCI must be unique within a DU; returns the first match */ +nr_rrc_cell_container_t *rrc_get_cell_by_pci_for_du(const seq_arr_t *cells, uint16_t pci) +{ + DevAssert(cells); + elm_arr_t elm = find_if((seq_arr_t *)cells, &pci, eq_pci_in_du); + if (elm.found) { + // elm.it points to an element in seq_arr, which stores nr_rrc_cell_container_t* + nr_rrc_cell_container_t **cell_ptr = (nr_rrc_cell_container_t **)elm.it; + return *cell_ptr; + } + return NULL; +} + +/** @brief Add cell to DU's cells array + * @param[in] cells Pointer to DU's cells sequence array + * @param[in] cell Pointer to cell container to add + * @return Pointer to the added cell, or NULL on duplicate cell_id/PCI or error + * @note Rejects duplicate cell_id or PCI within the DU */ +nr_rrc_cell_container_t *rrc_add_cell_to_du(seq_arr_t *cells, nr_rrc_cell_container_t *cell) +{ + DevAssert(cells); + DevAssert(cell); + + // Check for duplicate cell_id within this DU + nr_rrc_cell_container_t *exists = rrc_get_cell_for_du(cells, cell->info.cell_id); + if (exists) { + LOG_E(NR_RRC, "Trying to add an already existing cell with ID=%lu\n", cell->info.cell_id); + return NULL; + } + + // Check for duplicate PCI within this DU (PCI must be unique within a DU) + nr_rrc_cell_container_t *existing_pci = rrc_get_cell_by_pci_for_du(cells, cell->info.pci); + if (existing_pci != NULL) { + LOG_E(NR_RRC, + "Trying to add cell with ID=%lu, but PCI %d already exists in this DU (cell ID %lu)\n", + cell->info.cell_id, + cell->info.pci, + existing_pci->info.cell_id); + return NULL; + } + + seq_arr_push_back(cells, &cell, sizeof(nr_rrc_cell_container_t *)); + LOG_I(NR_RRC, "Added cell %lu to DU cells array (total cells = %ld)\n", cell->info.cell_id, seq_arr_size(cells)); + + return cell; +} + +/** @brief Free a cell container and all its associated resources + * @param[in] cell Pointer to the cell container to free + * @note Frees ASN.1 structures (mib, sib1, mtc) and the cell container itself + * @note Does NOT remove the cell from the tree - caller must do RB_REMOVE first */ +void rrc_free_cell_container(nr_rrc_cell_container_t *cell) +{ + DevAssert(cell); + ASN_STRUCT_FREE(asn_DEF_NR_MIB, cell->mib); + ASN_STRUCT_FREE(asn_DEF_NR_SIB1, cell->sib1); + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, cell->mtc); + free(cell); +} + +static bool eq_serving_cell_id(const void *vval, const void *vit) +{ + const uint8_t *id = (const uint8_t *)vval; + const ue_serving_cell_t *elem = (const ue_serving_cell_t *)vit; + return elem->serving_cell_id == *id; +} + +/** @brief Get serving cell from UE's serving_cells array by serving_cell_id + * @param[in] ue UE context + * @param[in] serving_cell_id ServCellIndex (0 = PCell, 1-31 = SCell) + * @return Pointer to serving cell entry, or NULL if not found */ +ue_serving_cell_t *rrc_get_ue_serving_cell_by_id(const gNB_RRC_UE_t *ue, uint8_t serving_cell_id) +{ + DevAssert(ue); + elm_arr_t elm = find_if((seq_arr_t *)&ue->serving_cells, &serving_cell_id, eq_serving_cell_id); + if (elm.found) + return (ue_serving_cell_t *)elm.it; + return NULL; +} + +/** @brief Add a serving cell to UE's serving_cells array + * @param[in] ue UE context + * @param[in] cell Cell container (nci and assoc_id taken from here) + * @param[in] serving_cell_id ServCellIndex (0 = PCell, 1-31 = SCell) + * @return Pointer to the added or existing entry, or NULL on error + * @note Uniqueness: (serving_cell_id, nci) must be unique. Same pair returns existing; + * same serving_cell_id, different nci returns NULL. */ +ue_serving_cell_t *rrc_add_ue_serving_cell(gNB_RRC_UE_t *ue, const nr_rrc_cell_container_t *cell, uint8_t serving_cell_id) +{ + DevAssert(ue); + DevAssert(serving_cell_id < RRC_MAX_NUM_SERVING_CELLS); + + // Check maximum number of items in list + size_t current_count = seq_arr_size(&ue->serving_cells); + if (current_count >= RRC_MAX_NUM_SERVING_CELLS) { + LOG_E(NR_RRC, "Maximum number of serving cells (%d) reached\n", RRC_MAX_NUM_SERVING_CELLS); + return NULL; + } + + // Check if serving cell with this serving_cell_id already exists + ue_serving_cell_t *existing = rrc_get_ue_serving_cell_by_id(ue, serving_cell_id); + if (existing != NULL) { + // If it's the same nci, return existing + if (existing->nci == cell->info.cell_id) { + LOG_D(NR_RRC, "Serving cell %d already has cell %ld\n", serving_cell_id, cell->info.cell_id); + return existing; + } + // Reject if already exists with different nci + LOG_E(NR_RRC, "Serving cell with serving_cell_id %d already exists with different nci\n", serving_cell_id); + return NULL; + } + + LOG_I(NR_RRC, + "Adding new %s (cell %ld, assoc_id %d)\n", + serving_cell_id == RRC_PCELL_INDEX ? "PCell" : "SCell", + cell->info.cell_id, + cell->assoc_id); + ue_serving_cell_t new_cell = {.nci = cell->info.cell_id, .serving_cell_id = serving_cell_id, .assoc_id = cell->assoc_id}; + + size_t elt_size = sizeof(ue_serving_cell_t); + if (serving_cell_id == RRC_PCELL_INDEX) { + /* PCell must match F1 UE data (serving_cells and F1 UE data stay in sync). */ + DevAssert(cu_exists_f1_ue_data(ue->rrc_ue_id)); + DevAssert(cu_get_f1_ue_data(ue->rrc_ue_id).du_assoc_id == cell->assoc_id); + /* Ensure PCell is always at index 0 in serving_cells */ + size_t n = seq_arr_size(&ue->serving_cells); + seq_arr_push_back(&ue->serving_cells, &new_cell, elt_size); + if (n > 0) { + /* If there are existing cells, shift them right to make room for the new PCell */ + void *first = seq_arr_front(&ue->serving_cells); + memmove((uint8_t *)first + elt_size, first, n * elt_size); + memcpy(first, &new_cell, elt_size); + } + } else { + /* SCells are appended after PCell (if any) */ + seq_arr_push_back(&ue->serving_cells, &new_cell, elt_size); + } + return rrc_get_ue_serving_cell_by_id(ue, serving_cell_id); +} + +/** @brief Remove serving cells that belong to a specific DU + * @param[in] ue UE context + * @param[in] assoc_id Association ID of the DU + * @note Removes all serving cells (including PCell) that belong to the specified DU. + * Uses stored assoc_id for O(k) comparison without cell lookup. */ +void rrc_remove_ue_scells_from_du(gNB_RRC_UE_t *ue, sctp_assoc_t assoc_id) +{ + DevAssert(ue); + LOG_I(NR_RRC, "Removing SCells from DU (assoc_id=%d) for UE %d\n", assoc_id, ue->rrc_ue_id); + + // Collect indices of SCells to delete (don't modify array while iterating) + uint32_t indices_to_delete[RRC_MAX_NUM_SERVING_CELLS]; + size_t num_to_delete = 0; + uint32_t index = 0; + + FOR_EACH_SEQ_ARR (ue_serving_cell_t *, scell, &ue->serving_cells) { + // Use stored assoc_id for direct comparison + if (scell->assoc_id == assoc_id) { + indices_to_delete[num_to_delete++] = index; + } + index++; + } + + // Now delete the collected serving cells from end to beginning for safety + for (size_t i = num_to_delete; i > 0; i--) { + uint32_t idx = indices_to_delete[i - 1]; + void *it = seq_arr_at(&ue->serving_cells, idx); + seq_arr_erase(&ue->serving_cells, it); + } + +} + +/** @brief Get PCell serving cell entry for a UE based on serving_cells + * @param[in] ue UE context + * @return Pointer to serving cell entry for PCell, or NULL if not found + * @note PCell is always at index 0. */ +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue) +{ + DevAssert(ue); + size_t count = seq_arr_size(&ue->serving_cells); + if (count == 0) + return NULL; + + /* PCell is always the first serving cell */ + const ue_serving_cell_t *first = seq_arr_front(&ue->serving_cells); + DevAssert(first != NULL); + DevAssert(first->serving_cell_id == RRC_PCELL_INDEX); + return first; +} + +/** @brief Get UE's PCell container from the global cell tree + * @param[in] rrc RRC instance (global cell tree) + * @param[in] ue UE context + * @return Pointer to PCell container, or NULL if UE has no PCell or cell not in tree */ +nr_rrc_cell_container_t *rrc_get_pcell_for_ue(gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue) +{ + DevAssert(rrc != NULL); + DevAssert(ue != NULL); + const ue_serving_cell_t *pcell_entry = ue_get_pcell_entry(ue); + if (pcell_entry == NULL) + return NULL; + return get_cell_by_cell_id(&rrc->cells, pcell_entry->nci); +} + diff --git a/openair2/RRC/NR/rrc_cell_management.h b/openair2/RRC/NR/rrc_cell_management.h new file mode 100644 index 0000000000..99071cceb2 --- /dev/null +++ b/openair2/RRC/NR/rrc_cell_management.h @@ -0,0 +1,73 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef RRC_CELL_MANAGEMENT_H_ +#define RRC_CELL_MANAGEMENT_H_ + +#include "nr_rrc_defs.h" // for nr_rrc_du_container_t, nr_rrc_cell_container_t, RB_HEAD +#include "common/utils/collection/tree.h" +#include +#include + +/** @brief ServCellIndex value for Primary Cell (TS 38.331) + * @note ServCellIndex is a short identity used to uniquely identify a serving cell + * (PCell, PSCell, or SCell) across cell groups. Value 0 applies for the PCell. */ +#define RRC_PCELL_INDEX 0 + +/** @brief Maximum number of serving cells (TS 38.331) + * @note maxNrofServingCells = 32 (1 PCell + up to 31 SCells) + * @note ServCellIndex range: INTEGER (0..maxNrofServingCells-1) = 0..31 */ +#define RRC_MAX_NUM_SERVING_CELLS 32 + +// Generate RB tree functions (prototypes) +int rrc_cell_cmp(const struct nr_rrc_cell_container_t *a, const struct nr_rrc_cell_container_t *b); +RB_PROTOTYPE(rrc_cell_tree, nr_rrc_cell_container_t, entries, rrc_cell_cmp); +int du_compare(const struct nr_rrc_du_container_t *a, const struct nr_rrc_du_container_t *b); +RB_PROTOTYPE(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); + +// DU management (global DU tree) +nr_rrc_du_container_t *rrc_add_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du); +void rrc_rm_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du); +void rrc_cleanup_du(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du); +void rrc_free_du_container(nr_rrc_du_container_t *du); +nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, const sctp_assoc_t assoc_id); + +// Cell management (global cell tree) +nr_rrc_cell_container_t *get_cell_by_cell_id(struct rrc_cell_tree *cells, const uint64_t cell_id); +nr_rrc_cell_container_t *rrc_add_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell); +void rrc_rm_cell(gNB_RRC_INST *rrc, nr_rrc_cell_container_t *cell); + +// Cell management (DU-specific cell tree) +nr_rrc_cell_container_t *rrc_get_cell_for_du(seq_arr_t *cells, uint64_t cell_id); +nr_rrc_cell_container_t *rrc_get_cell_by_pci_for_du(const seq_arr_t *cells, uint16_t pci); +nr_rrc_cell_container_t *rrc_add_cell_to_du(seq_arr_t *cells, nr_rrc_cell_container_t *cell); + +// Cell cleanup +void rrc_free_cell_container(nr_rrc_cell_container_t *cell); + +// UE serving cell functions +ue_serving_cell_t *rrc_add_ue_serving_cell(gNB_RRC_UE_t *ue, const nr_rrc_cell_container_t *cell, uint8_t serving_cell_id); +void rrc_remove_ue_scells_from_du(gNB_RRC_UE_t *ue, sctp_assoc_t assoc_id); +ue_serving_cell_t *rrc_get_ue_serving_cell_by_id(const gNB_RRC_UE_t *ue, uint8_t serving_cell_id); +const ue_serving_cell_t *ue_get_pcell_entry(const gNB_RRC_UE_t *ue); +nr_rrc_cell_container_t *rrc_get_pcell_for_ue(gNB_RRC_INST *rrc, const gNB_RRC_UE_t *ue); + +#endif /* RRC_CELL_MANAGEMENT_H_ */ diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 39c1d31490..1f9779478d 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -82,8 +82,10 @@ #include "openair2/F1AP/lib/f1ap_ue_context.h" #include "rrc_gNB_NGAP.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rrc_gNB_mobility.h" #include "rrc_gNB_radio_bearers.h" +#include "rrc_cell_management.h" #include "rrc_messages_types.h" #include "rrc_gNB_asn1.h" #include "seq_arr.h" @@ -369,6 +371,7 @@ void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, gNB_RrcConfigurationReq *c RB_INIT(&rrc->rrc_ue_head); RB_INIT(&rrc->cuups); RB_INIT(&rrc->dus); + RB_INIT(&rrc->cells); rrc->configuration = *configuration; } @@ -531,16 +534,18 @@ void activate_srb(gNB_RRC_UE_t *UE, int srb_id) * 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 cell (primary serving) cell container * @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 nr_rrc_cell_container_t *cell, const uint32_t *gNB_DU_ue_id) { f1ap_ue_context_setup_req_t req = { .gNB_CU_ue_id = ue->rrc_ue_id, - .servCellIndex = 0, + .servCellIndex = RRC_PCELL_INDEX, + .plmn = cell->info.plmn, + .nr_cellid = cell->info.cell_id, }; /* UE Aggregate Maximum Bit Rate Uplink is C-ifDRBSetup: 1 Gbps */ @@ -548,7 +553,7 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue *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); + req.cu_to_du_rrc_info.meas_timing_config = get_meas_timing_config(cell->mtc, ue->measConfig); /* UE capabilities */ if (ue->ue_cap_buffer.len > 0) { @@ -562,13 +567,6 @@ static f1ap_ue_context_setup_req_t rrc_fill_f1_ue_context_setup(gNB_RRC_UE_t *ue *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; } @@ -770,22 +768,24 @@ void free_RRCReconfiguration_params(nr_rrc_reconfig_param_t params) NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cellid) { - nr_rrc_du_container_t *du = get_du_by_cell_id((gNB_RRC_INST *)rrc, nr_cellid); - DevAssert(du != NULL); - f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&((gNB_RRC_INST *)rrc)->cells, nr_cellid); + DevAssert(cell != NULL); - if (du->mtc != NULL) { + if (cell->mtc != NULL) { NR_ReportConfigToAddMod_t *rc_PER = NULL; NR_ReportConfigToAddMod_t *rc_A2 = NULL; seq_arr_t rc_A3_seq = {0}; seq_arr_t neigh_seq = {0}; seq_arr_init(&rc_A3_seq, sizeof(NR_ReportConfigToAddMod_t)); seq_arr_init(&neigh_seq, sizeof(nr_neighbour_cell_t)); - int scs = get_ssb_scs(cell_info); - int band = get_dl_band(cell_info); - const NR_MeasTimingList_t *mtlist = du->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; + + // Get cell info from the cell container + int scs = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.scs : cell->info.fdd.dl.scs; + int band = cell->info.mode == NR_MODE_TDD ? cell->info.tdd.dlul.band : cell->info.fdd.dl.band; + + const NR_MeasTimingList_t *mtlist = cell->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming; const NR_MeasTiming_t *mt = mtlist->list.array[0]; - const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell_info->nr_cellid); + const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell->info.cell_id); seq_arr_t *neighbour_cells = NULL; if (neighbour_config) neighbour_cells = neighbour_config->neighbour_cells; @@ -835,7 +835,7 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell if (rrc->measurementConfiguration.a2_event) rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event); - NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell_info->nr_pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); + NR_MeasConfig_t *result = get_MeasConfig(mt, band, scs, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); // Clean up sequence arrays seq_arr_free(&rc_A3_seq, NULL); @@ -1058,12 +1058,14 @@ static void rrc_gNB_send_f1_drb_release_request(gNB_RRC_INST *rrc, gNB_RRC_UE_t f1ap_ue_context_mod_req_t req = { .gNB_CU_ue_id = ue_p->rrc_ue_id, .gNB_DU_ue_id = ue_data.secondary_ue, - .servCellIndex = 0, + .servCellIndex = RRC_PCELL_INDEX, }; req.plmn = malloc_or_fail(sizeof(*req.plmn)); *req.plmn = rrc->configuration.plmn[0]; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, ue_p); + DevAssert(cell != NULL); req.nr_cellid = malloc_or_fail(sizeof(*req.nr_cellid)); - *req.nr_cellid = rrc->nr_cellid; + *req.nr_cellid = cell->info.cell_id; req.drbs_rel = malloc_or_fail(sizeof(*req.drbs_rel)); req.drbs_rel_len = n_drb_to_release; memcpy(req.drbs_rel, drb_to_release, n_drb_to_release * sizeof(int)); @@ -1190,7 +1192,7 @@ static void cuup_notify_reestablishment(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue_p) * @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 rnti_t old_rnti, - const nr_rrc_du_container_t *du) + const nr_rrc_cell_container_t *cell) { module_id_t module_id = 0; gNB_RRC_INST *rrc = RC.nrrrc[module_id]; @@ -1198,16 +1200,19 @@ static void rrc_gNB_generate_RRCReestablishment(rrc_gNB_ue_context_t *ue_context uint8_t buffer[NR_RRC_BUF_SIZE] = {0}; uint8_t xid = rrc_gNB_get_next_transaction_identifier(module_id); ue_p->xids[xid] = RRC_REESTABLISH; - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - uint32_t ssb_arfcn = get_ssb_arfcn(du); - LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", cell_info->nr_pci, ssb_arfcn); + + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(rrc, ue_p); + DevAssert(pcell != NULL); + DevAssert(pcell->info.cell_id == cell->info.cell_id); + uint32_t ssb_arfcn = get_ssb_arfcn(pcell); + LOG_I(NR_RRC, "Reestablishment update key pci=%d, earfcn_dl=%u\n", pcell->info.pci, ssb_arfcn); /* Per TS 33.501 6.9.2.3.4: When sending RRCReestablishment with nextHopChainingCount, * we're staying at the same NCC level (not advancing), so use horizontal derivation * from the currently active KgNB. Vertical derivation (from NH) is only used when * advancing to a new NCC level, which happens during handover or masterKeyUpdate. */ const uint8_t *base_key = ue_p->kgnb; - nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, base_key, ue_p->kgnb); + nr_derive_key_ng_ran_star(pcell->info.pci, ssb_arfcn, base_key, ue_p->kgnb); int size = do_RRCReestablishment(ue_context_pP->ue_context.nh_ncc, buffer, NR_RRC_BUF_SIZE, xid); LOG_A(NR_RRC, "Send RRCReestablishment [%d bytes] to RNTI %04x\n", size, ue_p->rnti); @@ -1282,13 +1287,13 @@ static void rrc_gNB_process_RRCReestablishmentComplete(gNB_RRC_INST *rrc, /** 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); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, ue_p); + // UE should have a PCell set at this point (set when RRCReestablishmentRequest was handled) + if (cell == NULL) { + LOG_E(NR_RRC, "UE %d: no PCell for reestablishment on different DU\n", ue_p->rrc_ue_id); return; } - rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, du, NULL); + rrc_f1_ue_context_setup_for_target_du(rrc, ue_p, cell, 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 @@ -1404,9 +1409,17 @@ static void rrc_handle_RRCSetupRequest(gNB_RRC_INST *rrc, 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; + // Add PCell to serving_cells array + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cellid); + if (!cell) { + LOG_E(NR_RRC, "Cell %ld not found, rejecting UE\n", msg->nr_cellid); + rrc_gNB_generate_RRCReject(rrc, ue_context_p); + return; + } + /* Add PCell to serving_cells array */ + rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX); UE->ongoing_reconfiguration = false; - UE->measConfig = nr_rrc_get_measconfig(rrc, UE->nr_cellid); + UE->measConfig = nr_rrc_get_measconfig(rrc, msg->nr_cellid); activate_srb(UE, 1); rrc_gNB_generate_RRCSetup(0, msg->crnti, ue_context_p, msg->du2cu_rrc_container, msg->du2cu_rrc_container_length); } @@ -1434,7 +1447,8 @@ static rrc_gNB_ue_context_t *rrc_gNB_get_ue_context_source_cell(gNB_RRC_INST *rr if (!ue->ho_context || !ue->ho_context->source) continue; nr_ho_source_cu_t *source_ctx = ue->ho_context->source; - if (source_ctx->old_rnti == rntiP && source_ctx->du->setup_req->cell[0].info.nr_pci == pci) + const nr_rrc_cell_container_t *source_cell = source_ctx->cell; + if (source_cell && source_ctx->old_rnti == rntiP && source_cell->info.pci == pci) return ue_context_p; } return NULL; @@ -1454,6 +1468,28 @@ static void f1u_dl_gtp_rollback(gNB_RRC_UE_t *UE) } } +/** @brief Resolve previous/old cell (UE identity physCellId): same cell, else neighbor + * of current cell, else any cell with that PCI in this DU. */ +static const nr_rrc_cell_container_t *get_previous_cell_by_pci_in_du(gNB_RRC_INST *rrc, + const nr_rrc_cell_container_t *current_cell, + const nr_rrc_du_container_t *du, + uint16_t pci) +{ + if (current_cell->info.pci == pci) + return current_cell; + + const neighbour_cell_configuration_t *neigh_config = get_neighbour_cell_config(rrc, current_cell->info.cell_id); + if (neigh_config != NULL) { + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(neigh_config, pci); + if (neighbour != NULL) { + nr_rrc_cell_container_t *c = get_cell_by_cell_id(&rrc->cells, neighbour->nrcell_id); + if (c != NULL && c->assoc_id == du->assoc_id) + return c; + } + } + return rrc_get_cell_by_pci_for_du(&du->cells, pci); +} + static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id, const NR_RRCReestablishmentRequest_IEs_t *req, @@ -1484,15 +1520,17 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - if (du->mib == NULL || du->sib1 == NULL) { - /* we don't have MIB/SIB1 of the DU, and therefore cannot generate the - * Reestablishment (as we would need the SSB's ARFCN, which we cannot - * compute). So generate RRC Setup instead */ - LOG_E(NR_RRC, "Reestablishment request: no MIB/SIB1 of DU present, cannot do reestablishment, force setup request\n"); - goto fallback_rrc_setup; + // Fetch current cell: where the reestablishment request was received + nr_rrc_cell_container_t *current_cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cellid); + if (current_cell == NULL || current_cell->assoc_id != du->assoc_id) { + LOG_E(NR_RRC, + "received CCCH message, but no corresponding cell found for nr_cellid %ld in DU (assoc_id %d)\n", + msg->nr_cellid, + assoc_id); + return; } - if (du->mtc == NULL) { + if (current_cell->mtc == NULL) { // some UEs don't send MeasurementTimingConfiguration, so we don't know the // SSB ARFCN and can't do reestablishment. handle it gracefully by doing // RRC setup procedure instead @@ -1501,7 +1539,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, goto fallback_rrc_setup; } - // Try to find UE context by RNTI and assoc_id (normal case: same DU) + const nr_rrc_cell_container_t *cell = get_previous_cell_by_pci_in_du(rrc, current_cell, du, (uint16_t)physCellId); + if (cell == NULL) { + LOG_E(NR_RRC, "received CCCH message, but no corresponding cell found for PCI %ld in DU (assoc_id %d)\n", physCellId, assoc_id); + return; + } + ue_context_p = rrc_gNB_get_ue_context_by_rnti(rrc, assoc_id, old_rnti); if (ue_context_p == NULL) { // Fallback 1: Try to find UE by RNTI only (re-establishment on different DU scenario) @@ -1524,14 +1567,12 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, 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); - bool ho_reestab_on_source = source_ctx && previous_cell_info->nr_cellid == source_ctx->du->setup_req->cell[0].info.nr_cellid; + DevAssert(!source_ctx || source_ctx->cell != NULL); + const nr_rrc_cell_container_t *source_cell = source_ctx ? source_ctx->cell : NULL; + bool ho_reestab_on_source = source_cell ? cell->info.cell_id == source_cell->info.cell_id : false; if (ho_reestab_on_source) { /* the UE came back on the source DU while doing handover, release at @@ -1551,11 +1592,11 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, store_cgc(UE, &source_ctx->old_cgc); /* update to old DU assoc id -- RNTI + secondary DU UE ID further below */ - ue_data.du_assoc_id = source_ctx->du->assoc_id; + ue_data.du_assoc_id = source_cell->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) { + } else if (physCellId != cell->info.pci) { /* 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. @@ -1567,7 +1608,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *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, + cell->info.pci, ue_data.du_assoc_id, assoc_id); @@ -1606,7 +1647,7 @@ static void rrc_handle_RRCReestablishmentRequest(gNB_RRC_INST *rrc, 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); + cell->info.pci); ngap_cause = NGAP_CAUSE_RADIO_NETWORK_RELEASE_DUE_TO_NGRAN_GENERATED_REASON; goto fallback_rrc_setup; } @@ -1616,12 +1657,24 @@ 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; + + /* Update PCell in serving_cells array */ + DevAssert(cell->info.cell_id == msg->nr_cellid); + // Reset the serving set to the new PCell, remove all serving cells from the current PCell's DU. + const ue_serving_cell_t *existing_pcell = ue_get_pcell_entry(UE); + DevAssert(existing_pcell != NULL); + rrc_remove_ue_scells_from_du(UE, existing_pcell->assoc_id); + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, "Reestablishment: failed to add PCell (cell %ld), rejecting reestablishment\n", cell->info.cell_id); + goto fallback_rrc_setup; + } + 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, old_rnti, du); + rrc_gNB_generate_RRCReestablishment(ue_context_p, old_rnti, cell); return; fallback_rrc_setup: @@ -1713,13 +1766,34 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, neighbour_pci, neighbourCellRSRP); - const f1ap_served_cell_info_t *neigh_cell = get_cell_information_by_phycellId(neighbour_pci); - const f1ap_served_cell_info_t *serving_cell = get_cell_information_by_phycellId(scell_pci); - const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, serving_cell->nr_cellid); + // Resolve source cell by PCI from UE's serving cells + const nr_rrc_cell_container_t *source_cell = NULL; + FOR_EACH_SEQ_ARR (ue_serving_cell_t *, scell, &ue->serving_cells) { + nr_rrc_cell_container_t *cell_by_id = get_cell_by_cell_id(&rrc->cells, scell->nci); + if (cell_by_id != NULL && cell_by_id->info.pci == scell_pci) { + source_cell = cell_by_id; + break; + } + } + if (source_cell == NULL) { + LOG_W(NR_RRC, + "UE %d: received measurement report but source cell with PCI %d not found in UE's serving cells, skipping\n", + ue->rrc_ue_id, + scell_pci); + continue; + } + + // Resolve target cell via neighbour configuration + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, source_cell->info.cell_id); const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + const nr_rrc_cell_container_t *target_cell = NULL; + if (neighbour != NULL) { + // Resolve target cell by cell_id from neighbour config (may be NULL if no F1 connection) + target_cell = get_cell_by_cell_id(&rrc->cells, neighbour->nrcell_id); + } // CU does not have f1 connection with neighbour cell context. So check does serving cell has this phyCellId as a // neighbour. - if (!neigh_cell && neighbour) { + if (!target_cell && neighbour) { // No F1 connection but static neighbour configuration is available const nr_a3_event_t *a3_event_configuration = get_a3_configuration(rrc, neighbour->physicalCellId); /* if no A3 event configured for this physical cell ID, try to get the default one */ @@ -1738,12 +1812,9 @@ static void process_Event_Based_Measurement_Report(gNB_RRC_INST *rrc, } LOG_D(NR_RRC, "HO LOG: Trigger N2 HO for the neighbour gnb: %u cell: %lu\n", neighbour->gNB_ID, neighbour->nrcell_id); } - } else if (neigh_cell && neighbour) { + } else if (target_cell && neighbour) { /* we know the cell and are connected to the DU! */ - nr_rrc_du_container_t *source_du = get_du_by_cell_id(rrc, serving_cell->nr_cellid); - DevAssert(source_du); - nr_rrc_du_container_t *target_du = get_du_by_cell_id(rrc, neigh_cell->nr_cellid); - nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); + nr_rrc_trigger_f1_ho(rrc, ue, source_cell, target_cell); } else { LOG_W(NR_RRC, "UE %d: received A3 event for stronger neighbor PCI %d, but no such neighbour in configuration\n", ue->rrc_ue_id, neighbour_pci); } @@ -2612,12 +2683,15 @@ static void rrc_CU_process_ue_context_release_request(MessageDef *msg_p, sctp_as gNB_RRC_UE_t *UE = &ue_context_p->ue_context; if (UE->ho_context != NULL) { nr_ho_source_cu_t *source_ctx = UE->ho_context->source; - bool from_source_du = source_ctx && source_ctx->du->assoc_id == assoc_id; + bool from_source_du = source_ctx && source_ctx->cell->assoc_id == assoc_id; if (from_source_du) { // we received release request from the source DU, but HO is still // ongoing; free the UE, and remove the HO context. - LOG_W(NR_RRC, "UE %d: release request from source DU ID %ld during HO, marking HO as complete\n", UE->rrc_ue_id, source_ctx->du->setup_req->gNB_DU_id); - RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id); + LOG_W(NR_RRC, + "UE %d: release request from source cell PCI %d during HO, marking HO as complete\n", + UE->rrc_ue_id, + source_ctx->cell->info.pci); + RETURN_IF_INVALID_ASSOC_ID(source_ctx->cell->assoc_id); f1ap_ue_context_rel_cmd_t cmd = { .gNB_CU_ue_id = UE->rrc_ue_id, .gNB_DU_ue_id = source_ctx->du_ue_id, @@ -2663,6 +2737,7 @@ static void rrc_delete_ue_data(gNB_RRC_UE_t *UE) UE->redcap_cap = NULL; seq_arr_free(&UE->pduSessions, free_pdusession); seq_arr_free(&UE->drbs, free_drb); + seq_arr_free(&UE->serving_cells, NULL); } void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p) @@ -2744,6 +2819,10 @@ static void rrc_CU_process_ue_context_modification_response(MessageDef *msg_p, i nr_ho_source_cu_t *source_ctx = UE->ho_context->source; DevAssert(source_ctx->old_rnti == UE->rnti); nr_rrc_apply_target_context(UE); + /* Update cell association after handover */ + if (!nr_rrc_update_cell_assoc_after_ho(rrc, UE)) { + return; + } } } @@ -2765,7 +2844,9 @@ static void rrc_CU_process_ue_modification_required(MessageDef *msg_p, instance_ } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - if (UE->ho_context && UE->ho_context->source && UE->ho_context->source->du && UE->ho_context->source->du->assoc_id == assoc_id) { + bool on_source_du = UE->ho_context && UE->ho_context->source && UE->ho_context->source->cell + && UE->ho_context->source->cell->assoc_id == assoc_id; + if (on_source_du) { LOG_W(NR_RRC, "UE %d: UE Context Modification Required during handover, ignoring message\n", UE->rrc_ue_id); return; } @@ -3642,14 +3723,14 @@ static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t * - Handover preparation information (if provided) * @param rrc RRC instance * @param ue UE context - * @param du Target DU container containing cell and setup information + * @param cell Target cell container containing cell 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 nr_rrc_cell_container_t *cell, const byte_array_t *ho_prep_info) { - DevAssert(du != NULL); + DevAssert(cell != 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. @@ -3671,7 +3752,7 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, 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); + LOG_I(NR_RRC, "Triggering UE Context Setup for UE %d on DU %d\n", ue->rrc_ue_id, cell->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)); @@ -3685,12 +3766,12 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, /* 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); + ue->measConfig = nr_rrc_get_measconfig(rrc, cell->info.cell_id); 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); + f1ap_ue_context_setup_req_t req = rrc_fill_f1_ue_context_setup(ue, cell, opt_du_ue_id); /* Fill target DU specific fields */ req.srbs_len = 2; req.srbs = srbs; @@ -3699,8 +3780,8 @@ void rrc_f1_ue_context_setup_for_target_du(const gNB_RRC_INST *rrc, 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); + RETURN_IF_INVALID_ASSOC_ID(cell->assoc_id); + rrc->mac_rrc.ue_context_setup_request(cell->assoc_id, &req); free_ue_context_setup_req(&req); } @@ -3725,7 +3806,8 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, AssertFatal(!NODE_IS_DU(rrc->node_type), "illegal node type DU!\n"); - nr_rrc_du_container_t *du = get_du_for_ue((gNB_RRC_INST *)rrc, ue_p->rrc_ue_id); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue((gNB_RRC_INST *)rrc, ue_p); + DevAssert(cell != NULL); int nb_srb = 1; f1ap_srb_to_setup_t *srbs = calloc_or_fail(nb_srb, sizeof(*srbs)); @@ -3742,7 +3824,7 @@ void rrc_f1_ue_context_setup_from_e1_response(const gNB_RRC_INST *rrc, 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); + f1ap_ue_context_setup_req_t ue_context_setup_req = rrc_fill_f1_ue_context_setup(ue_p, cell, &du_ue_id); /* Fill bearers information for serving DU */ ue_context_setup_req.srbs_len = nb_srb; ue_context_setup_req.srbs = srbs; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index f91e346f02..f724c0a0ff 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -90,7 +90,9 @@ #include "uper_encoder.h" #include "rrc_gNB_mobility.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "common/utils/alg/find.h" +#include "common/utils/nr/nr_common.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -254,8 +256,9 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC /* UE is connected to only one PLMN at a time: store this as the serving PLMN */ UE->serving_plmn = req->plmn; - // Cell ID (NR CGI) - req->nr_cell_id = UE->nr_cellid; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + DevAssert(cell); + req->nr_cell_id = cell->info.cell_id; // PLMN (NR CGI and TAI) plmn_id_t *p = &req->plmn; LOG_I(NGAP, "Selected PLMN in the NG Initial UE Message: MCC=%03d MNC=%0*d\n", p->mcc, p->mnc_digit_length, p->mnc); @@ -766,8 +769,10 @@ void rrc_gNB_send_NGAP_UPLINK_NAS(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const NR_ NGAP_UPLINK_NAS(msg_p).nas_pdu.buf = buf; /* Fill PLMN and location info: use serving PLMN of the UE */ NGAP_UPLINK_NAS(msg_p).plmn = UE->serving_plmn; - NGAP_UPLINK_NAS(msg_p).nr_cell_id = rrc->nr_cellid; - NGAP_UPLINK_NAS(msg_p).tac = rrc->configuration.tac; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + DevAssert(cell); + NGAP_UPLINK_NAS(msg_p).nr_cell_id = cell->info.cell_id; + NGAP_UPLINK_NAS(msg_p).tac = cell->info.tac != 0 ? cell->info.tac : rrc->configuration.tac; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1128,10 +1133,11 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } - struct nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, msg->nr_cell_id); - if (du == NULL) { + // Get cell by cell_id + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(&rrc->cells, msg->nr_cell_id); + if (cell == NULL) { /* Cell Not Found! Return HO Request Failure*/ - LOG_E(RRC, "Failed to process Handover Request: no DU found with NR Cell ID=%lu \n", msg->nr_cell_id); + LOG_E(RRC, "Failed to process Handover Request: no cell found with NR Cell ID=%lu \n", msg->nr_cell_id); ngap_handover_failure_t fail = { .amf_ue_ngap_id = msg->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, @@ -1141,6 +1147,18 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } + struct nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, cell->assoc_id); + if(du == NULL) { + LOG_E(NR_RRC, "Failed to process Handover Request: no DU found with assoc_id=%d\n", cell->assoc_id); + ngap_handover_failure_t fail = { + .amf_ue_ngap_id = msg->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM, + }; + rrc_gNB_send_NGAP_HANDOVER_FAILURE(rrc, &fail); + return -1; + } + // Validate PLMN from GUAMI against allowed PLMN list const plmn_id_t *serving_plmn = get_serving_plmn(rrc, &msg->guami.plmn); if (!serving_plmn) { @@ -1154,16 +1172,16 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga return -1; } - uint16_t pci = du->setup_req->cell[0].info.nr_pci; + uint16_t pci = cell->info.pci; LOG_I(NR_RRC, "Received Handover Request (on NR Cell ID=%lu, PCI=%u) \n", msg->nr_cell_id, pci); // Create UE context - sctp_assoc_t curr_assoc_id = du->assoc_id; - rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(curr_assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX); + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_create_ue_context(du->assoc_id, UINT16_MAX, rrc, UINT64_MAX, UINT32_MAX); gNB_RRC_UE_t *UE = &ue_context_p->ue_context; // allocate context for target UE->ho_context = alloc_ho_ctx(HO_CTX_TARGET); + UE->ho_context->target->cell = cell; UE->ho_context->target->ho_trigger = nr_rrc_trigger_n2_ho_target; // Store IDs in UE context @@ -1185,9 +1203,9 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga // Reset KgNB memset(UE->kgnb, 0, SECURITY_KEY_LENGTH); // Derive KgNB* - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - uint32_t ssb_arfcn = get_ssb_arfcn(du); - nr_derive_key_ng_ran_star(cell_info->nr_pci, ssb_arfcn, UE->nh, UE->kgnb); + const nr_rrc_cell_info_t *cell_info = &cell->info; + uint32_t ssb_arfcn = get_ssb_arfcn(cell); + nr_derive_key_ng_ran_star(cell_info->pci, ssb_arfcn, UE->nh, UE->kgnb); UE->as_security_active = true; // Activate SRBs activate_srb(UE, SRB1); @@ -1474,24 +1492,28 @@ void rrc_gNB_send_NGAP_HANDOVER_REQUEST_ACKNOWLEDGE(gNB_RRC_INST *rrc, gNB_RRC_U void rrc_gNB_send_NGAP_HANDOVER_NOTIFY(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) { LOG_I(NR_RRC, "Triggering NGAP Handover Notify\n"); - nr_rrc_du_container_t *du = get_du_by_cell_id(rrc, rrc->nr_cellid); + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(rrc, UE); + if (cell == NULL) { + LOG_E(NR_RRC, "Failed to send Handover Notify: no PCell found for UE %d\n", UE->rrc_ue_id); + return; + } + nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); if (du == NULL) { - LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found with NR Cell ID=%lu\n", rrc->nr_cellid); + LOG_E(NR_RRC, "Failed to send Handover Notify: no DU found for UE %d\n", UE->rrc_ue_id); return; } - MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_HANDOVER_NOTIFY); ngap_handover_notify_t *ho_notify = &NGAP_HANDOVER_NOTIFY(msg_p); memset(ho_notify, 0, sizeof(*ho_notify)); ho_notify->gNB_ue_ngap_id = UE->rrc_ue_id; ho_notify->amf_ue_ngap_id = UE->amf_ue_ngap_id; - ho_notify->user_info.nrCellIdentity = rrc->nr_cellid; + ho_notify->user_info.nrCellIdentity = cell->info.cell_id; target_ran_node_id_t *target_ng_ran = &ho_notify->user_info.target_ng_ran; - target_ng_ran->tac = *du->setup_req->cell->info.tac; + target_ng_ran->tac = cell->info.tac; target_ng_ran->targetgNBId = rrc->node_id; - target_ng_ran->plmn_identity = du->setup_req->cell->info.plmn; + target_ng_ran->plmn_identity = cell->info.plmn; itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); } @@ -1628,7 +1650,23 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) if (NODE_IS_CU(RC.nrrrc[instance]->node_type)) { MessageDef *m = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_PAGING_IND); F1AP_PAGING_IND(m).plmn = *req_plmn; - F1AP_PAGING_IND (m).nr_cellid = RC.nrrrc[j]->nr_cellid; + // Construct 5G-S-TMSI-Part1 from paging identity to find UE + const fiveg_s_tmsi_t *s_tmsi = &msg->ue_paging_identity.s_tmsi; + uint64_t s_tmsi_part1 = nr_construct_5g_s_tmsi_part1(s_tmsi->amf_set_id, s_tmsi->amf_pointer, s_tmsi->m_tmsi); + // Try to find UE by 5G-S-TMSI-Part1 + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_ue_context_5g_s_tmsi_exist(RC.nrrrc[instance], s_tmsi_part1); + if (ue_context_p == NULL) { + LOG_W(NR_RRC, "UE not found by paging identity\n"); + return -1; + } + // UE found: use its associated cell + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + nr_rrc_cell_container_t *cell = rrc_get_pcell_for_ue(RC.nrrrc[instance], UE); + if (cell == NULL) { + LOG_W(NR_RRC, "UE %d (rnti %04x) found by paging identity but has no valid cell\n", UE->rrc_ue_id, UE->rnti); + return -1; + } + F1AP_PAGING_IND(m).nr_cellid = cell->info.cell_id; F1AP_PAGING_IND(m).ueidentityindexvalue = (uint16_t)(msg->ue_paging_identity.s_tmsi.m_tmsi % 1024); F1AP_PAGING_IND(m).fiveg_s_tmsi = msg->ue_paging_identity.s_tmsi.m_tmsi; F1AP_PAGING_IND(m).paging_drx = msg->paging_drx; diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index c05b8a33a8..d9a5a727c9 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -41,6 +41,7 @@ #include "openair2/F1AP/f1ap_ids.h" #include "tree.h" #include "rrc_gNB_radio_bearers.h" +#include "rrc_cell_management.h" static void rrc_gNB_ue_context_update_time(rrc_gNB_ue_context_t *ctxt) { @@ -155,10 +156,11 @@ void rrc_gNB_remove_ue_context(gNB_RRC_INST *rrc_instance_pP, rrc_gNB_ue_context return; } - LOG_UE_EVENT(&ue_context_pP->ue_context, "Remove UE context\n"); + gNB_RRC_UE_t *ue = &ue_context_pP->ue_context; + LOG_I(NR_RRC, "[--] (UE ID %d RNTI %04x) Remove UE context\n", ue->rrc_ue_id, ue->rnti); RB_REMOVE(rrc_nr_ue_tree_s, &rrc_instance_pP->rrc_ue_head, ue_context_pP); - uid_linear_allocator_free(&rrc_instance_pP->uid_allocator, ue_context_pP->ue_context.rrc_ue_id - 1); - cu_remove_f1_ue_data(ue_context_pP->ue_context.rrc_ue_id); + uid_linear_allocator_free(&rrc_instance_pP->uid_allocator, ue->rrc_ue_id - 1); + cu_remove_f1_ue_data(ue->rrc_ue_id); rrc_gNB_free_mem_ue_context(ue_context_pP); } @@ -225,6 +227,8 @@ rrc_gNB_ue_context_t *rrc_gNB_create_ue_context(sctp_assoc_t assoc_id, seq_arr_init(&ue->pduSessions, sizeof(rrc_pdu_session_param_t)); // Initialise setup DRBs list seq_arr_init(&ue->drbs, sizeof(drb_t)); + // Initialise serving cells list + seq_arr_init(&ue->serving_cells, sizeof(ue_serving_cell_t)); RB_INSERT(rrc_nr_ue_tree_s, &rrc_instance_pP->rrc_ue_head, ue_context_p); LOG_UE_EVENT(ue, diff --git a/openair2/RRC/NR/rrc_gNB_cuup.c b/openair2/RRC/NR/rrc_gNB_cuup.c index 1e3df71822..8292699a5c 100644 --- a/openair2/RRC/NR/rrc_gNB_cuup.c +++ b/openair2/RRC/NR/rrc_gNB_cuup.c @@ -43,6 +43,7 @@ #include "tree.h" #include "e1ap_interface_management.h" #include "rrc_gNB_NGAP.h" +#include "rrc_cell_management.h" static int cuup_compare(const nr_rrc_cuup_container_t *a, const nr_rrc_cuup_container_t *b) { diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 246df60626..10d2e4131c 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -20,6 +20,7 @@ */ #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include #include #include @@ -52,12 +53,12 @@ #include "utils.h" #include "xer_encoder.h" -int get_dl_band(const struct f1ap_served_cell_info_t *cell_info) +static int get_dl_band(const struct f1ap_served_cell_info_t *cell_info) { return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.freqinfo.band : cell_info->fdd.dl_freqinfo.band; } -int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info) +static int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info) { return cell_info->mode == F1AP_MODE_TDD ? cell_info->tdd.tbw.scs : cell_info->fdd.dl_tbw.scs; } @@ -71,6 +72,9 @@ static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc) return ssb_mtc; } +/** @brief Return the frequency of the SS block of the cell for which this message is included, + * or of other SS blocks within the same carrier. MeasurementTimingConfiguration message is + * used to convey assistance information for measurement timing between nodes (e.g F1 setup) */ static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc) { /* format has been verified when accepting MeasurementTimingConfiguration */ @@ -78,24 +82,17 @@ static int ssb_arfcn_mtc(const NR_MeasurementTimingConfiguration_t *mtc) return mtlist->list.array[0]->frequencyAndTiming->carrierFreq; } -int get_ssb_arfcn(const struct nr_rrc_du_container_t *du) +/** @brief Find first cell owned by this DU (assoc_id) and return its SSB ARFCN + * @note Current assumption is that each DU serves only one cell*/ +int get_ssb_arfcn(const struct nr_rrc_cell_container_t *cell) { - DevAssert(du != NULL && du->mtc != NULL); - return ssb_arfcn_mtc(du->mtc); + DevAssert(cell != NULL); + if (cell->mtc != NULL) + return ssb_arfcn_mtc(cell->mtc); + AssertFatal(false, "no mtc found for cell %ld\n", cell->info.cell_id); + return 0; } -static int du_compare(const nr_rrc_du_container_t *a, const nr_rrc_du_container_t *b) -{ - if (a->assoc_id > b->assoc_id) - return 1; - if (a->assoc_id == b->assoc_id) - return 0; - return -1; /* a->assoc_id < b->assoc_id */ -} - -/* Tree management functions */ -RB_GENERATE/*_STATIC*/(rrc_du_tree, nr_rrc_du_container_t, entries, du_compare); - static bool rrc_gNB_plmn_matches(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *info) { const gNB_RrcConfigurationReq *conf = &rrc->configuration; @@ -172,33 +169,17 @@ static int nr_cell_id_match(const void *key, const void *element) return 1; } -static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const f1ap_served_cell_info_t *cell_info) +static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell) { void *base = seq_arr_front(rrc->neighbour_cell_configuration); size_t nmemb = seq_arr_size(rrc->neighbour_cell_configuration); size_t size = sizeof(neighbour_cell_configuration_t); - void *it = bsearch((void *)&cell_info->nr_cellid, base, nmemb, size, nr_cell_id_match); + void *it = bsearch((void *)&cell->info.cell_id, base, nmemb, size, nr_cell_id_match); return (neighbour_cell_configuration_t *)it; } -const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId) -{ - gNB_RRC_INST *rrc = RC.nrrrc[0]; - nr_rrc_du_container_t *it = NULL; - RB_FOREACH (it, rrc_du_tree, &rrc->dus) { - for (int cellIdx = 0; cellIdx < it->setup_req->num_cells_available; cellIdx++) { - const f1ap_served_cell_info_t *cell_info = &(it->setup_req->cell[cellIdx].info); - if (cell_info->nr_pci == phyCellId) { - LOG_D(NR_RRC, "HO LOG: Found cell with phyCellId %d\n", phyCellId); - return cell_info; - } - } - } - return NULL; -} - static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) { uint32_t *ssb_arfcn_ptr = (uint32_t *)ssb_arfcn; @@ -212,28 +193,36 @@ static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) /** * @brief Labels neighbour cells if they are intra frequency to prepare meas config only for intra frequency ho * @param[in] rrc Pointer to RRC instance - * @param[in] cell_info Pointer to cell information + * @param[in] cell Pointer to cell container */ -static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, - const nr_rrc_du_container_t *du, - const f1ap_served_cell_info_t *cell_info) +static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell) { if (!rrc->neighbour_cell_configuration) return; - neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell_info); + neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell); if (!neighbour_cell_config) return; - LOG_D(NR_RRC, "HO LOG: Cell: %lu has neighbour cell configuration!\n", cell_info->nr_cellid); - uint32_t ssb_arfcn = get_ssb_arfcn(du); + uint32_t ssb_arfcn = get_ssb_arfcn(cell); + LOG_D(NR_RRC, + "Cell %lu (PCI %d, SSB ARFCN %u) has neighbour cell configuration, labeling intra-frequency neighbours\n", + cell->info.cell_id, + cell->info.pci, + ssb_arfcn); seq_arr_t *cell_neighbour_list = neighbour_cell_config->neighbour_cells; for_each(cell_neighbour_list, (void *)&ssb_arfcn, is_intra_frequency_neighbour); } -static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell, int ssb_arfcn) +static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_configuration, const f1ap_served_cell_info_t *cell) { + // MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so + // "tolerate" this behavior, despite it being mandatory + NR_MeasurementTimingConfiguration_t *mtc = extract_mtc(cell->measurement_timing_config, cell->measurement_timing_config_len); + int ssb_arfcn = ssb_arfcn_mtc(mtc); + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); + for (int c = 0; c < neighbour_cell_configuration->size; c++) { const neighbour_cell_configuration_t *neighbour_config = seq_arr_at(neighbour_cell_configuration, c); for (int ni = 0; ni < neighbour_config->neighbour_cells->size; ni++) { @@ -289,6 +278,44 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config return true; } +static void cp_f1_served_cell_info_to_cell(nr_rrc_cell_container_t *dst, const f1ap_served_cell_info_t *src) +{ + DevAssert(dst != NULL); + DevAssert(src != NULL); + + // Copy basic cell information + dst->info.cell_id = src->nr_cellid; + dst->info.pci = src->nr_pci; + dst->info.plmn = src->plmn; + dst->info.tac = src->tac ? *src->tac : 0; + + // Copy frequency information based on mode + if (src->mode == F1AP_MODE_TDD) { + dst->info.mode = NR_MODE_TDD; + dst->info.tdd.dlul.band = src->tdd.freqinfo.band; + dst->info.tdd.dlul.arfcn = src->tdd.freqinfo.arfcn; + dst->info.tdd.dlul.scs = src->tdd.tbw.scs; + dst->info.tdd.dlul.nrb = src->tdd.tbw.nrb; + } else { + dst->info.mode = NR_MODE_FDD; + /* DL */ + dst->info.fdd.dl.band = src->fdd.dl_freqinfo.band; + dst->info.fdd.dl.arfcn = src->fdd.dl_freqinfo.arfcn; + dst->info.fdd.dl.scs = src->fdd.dl_tbw.scs; + dst->info.fdd.dl.nrb = src->fdd.dl_tbw.nrb; + /* UL */ + dst->info.fdd.ul.band = src->fdd.ul_freqinfo.band; + dst->info.fdd.ul.arfcn = src->fdd.ul_freqinfo.arfcn; + dst->info.fdd.ul.scs = src->fdd.ul_tbw.scs; + dst->info.fdd.ul.nrb = src->fdd.ul_tbw.nrb; + } + + // Extract and store MTC + if (src->measurement_timing_config_len > 0) { + dst->mtc = extract_mtc(src->measurement_timing_config, src->measurement_timing_config_len); + } +} + void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) { AssertFatal(assoc_id != 0, "illegal assoc_id == 0: should be -1 (monolithic) or >0 (split)\n"); @@ -300,147 +327,147 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) f1ap_setup_failure_t fail = {.transaction_id = F1AP_get_next_transaction_identifier(0, 0)}; // check: - // - it is one cell // - PLMN and Cell ID matches // - no previous DU with the same ID // else reject - if (req->num_cells_available != 1) { - LOG_E(NR_RRC, "can only handle on DU cell, but gNB_DU %ld has %d\n", req->gNB_DU_id, req->num_cells_available); - fail.cause = F1AP_CauseRadioNetwork_gNB_CU_Cell_Capacity_Exceeded; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } - f1ap_served_cell_info_t *cell_info = &req->cell[0].info; - if (!rrc_gNB_plmn_matches(rrc, cell_info)) { - LOG_E(NR_RRC, - "PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n", - rrc->configuration.plmn[0].mcc, - rrc->configuration.plmn[0].mnc_digit_length, - rrc->configuration.plmn[0].mnc, - cell_info->plmn.mcc, - cell_info->plmn.mnc_digit_length, - cell_info->plmn.mnc); - fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } nr_rrc_du_container_t *it = NULL; RB_FOREACH(it, rrc_du_tree, &rrc->dus) { - if (it->setup_req->gNB_DU_id == req->gNB_DU_id) { + if (it->gNB_DU_id == req->gNB_DU_id) { LOG_E(NR_RRC, "gNB-DU ID: existing DU %s on assoc_id %d already has ID %ld, rejecting requesting gNB-DU\n", - it->setup_req->gNB_DU_name, + it->gNB_DU_name, it->assoc_id, - it->setup_req->gNB_DU_id); - fail.cause = F1AP_CauseMisc_unspecified; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - return; - } - // note: we assume that each DU contains only one cell; otherwise, we would - // need to check every cell in the requesting DU to any existing cell. - const f1ap_served_cell_info_t *exist_info = &it->setup_req->cell[0].info; - const f1ap_served_cell_info_t *new_info = &req->cell[0].info; - if (exist_info->nr_cellid == new_info->nr_cellid || exist_info->nr_pci == new_info->nr_pci) { - LOG_E(NR_RRC, - "existing DU %s on assoc_id %d already has cellID %ld/physCellId %d, rejecting requesting gNB-DU with cellID %ld/physCellId %d\n", - it->setup_req->gNB_DU_name, - it->assoc_id, - exist_info->nr_cellid, - exist_info->nr_pci, - new_info->nr_cellid, - new_info->nr_pci); + it->gNB_DU_id); fail.cause = F1AP_CauseMisc_unspecified; rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); return; } } + LOG_I(NR_RRC, "Accepting DU %ld (%s) (RRC version %u.%u.%u)\n", req->gNB_DU_id, req->gNB_DU_name, req->rrc_ver[0], req->rrc_ver[1], req->rrc_ver[2]); - // MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so - // "tolerate" this behavior, despite it being mandatory - NR_MeasurementTimingConfiguration_t *mtc = - extract_mtc(cell_info->measurement_timing_config, cell_info->measurement_timing_config_len); - - if (rrc->neighbour_cell_configuration - && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info, ssb_arfcn_mtc(mtc))) { - LOG_E(NR_RRC, "problem with DU %ld in neighbor configuration, rejecting DU\n", req->gNB_DU_id); - f1ap_setup_failure_t fail = {.cause = F1AP_CauseMisc_unspecified}; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); - return; - } - - const f1ap_gnb_du_system_info_t *sys_info = req->cell[0].sys_info; - NR_MIB_t *mib = NULL; - NR_SIB1_t *sib1 = NULL; - - if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) { - if (!extract_sys_info(sys_info, &mib, &sib1)) { - LOG_W(NR_RRC, "rejecting DU ID %ld\n", req->gNB_DU_id); - fail.cause = F1AP_CauseProtocol_semantic_error; - rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, mtc); - return; - } - } - LOG_I(NR_RRC, "Accepting DU %ld (%s), sending F1 Setup Response\n", req->gNB_DU_id, req->gNB_DU_name); - LOG_I(NR_RRC, "DU uses RRC version %u.%u.%u\n", req->rrc_ver[0], req->rrc_ver[1], req->rrc_ver[2]); - - // we accept the DU - nr_rrc_du_container_t *du = calloc(1, sizeof(*du)); - AssertFatal(du, "out of memory\n"); + // DU is accepted, add it to tree and add cell to DU's cell array + nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); du->assoc_id = assoc_id; + du->gNB_DU_id = req->gNB_DU_id; + du->gNB_DU_name = strdup(req->gNB_DU_name ? req->gNB_DU_name : "N/A"); + memcpy(du->rrc_ver, req->rrc_ver, sizeof(du->rrc_ver)); - /* ITTI will free the setup request message via free(). So the memory - * "inside" of the message will remain, but the "outside" container no, so - * allocate memory and copy it in */ - du->setup_req = calloc(1,sizeof(*du->setup_req)); - AssertFatal(du->setup_req, "out of memory\n"); - // Copy F1AP message - *du->setup_req = cp_f1ap_setup_request(req); - // MIB can be null and configured later via DU Configuration Update - du->mib = mib; - du->sib1 = sib1; - du->mtc = mtc; - RB_INSERT(rrc_du_tree, &rrc->dus, du); - rrc->num_dus++; - - served_cells_to_activate_t cell = { - .plmn = cell_info->plmn, - .nr_cellid = cell_info->nr_cellid, - .nrpci = cell_info->nr_pci, - .num_SI = 0, - }; - - // Encode CU SIBs and configure setup response with sysinfo - seq_arr_t *sibs = rrc->SIBs; - if (sibs) { - for (int i = 0; i < sibs->size; i++) { - nr_SIBs_t *sib = (nr_SIBs_t *)seq_arr_at(sibs, i); - switch (sib->SIB_type) { - case 2: { - NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(mtc); - sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer, ssbmtc); - cell.SI_msg[cell.num_SI].SI_container = sib->SIB_buffer; - cell.SI_msg[cell.num_SI].SI_container_length = sib->SIB_size; - cell.SI_msg[cell.num_SI].SI_type = sib->SIB_type; - cell.num_SI++; - } break; - default: - AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); - } - } - } - - if (du->mib != NULL && du->sib1 != NULL) - label_intra_frequency_neighbours(rrc, du, cell_info); + // Initialize cell array for this DU + seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *)); + nr_rrc_du_container_t *du_collision = rrc_add_du(rrc, du); + AssertFatal(du_collision == NULL, "rrc_add_du should succeed for new DU (assoc_id %d)", assoc_id); + // Build F1 Setup Response f1ap_setup_resp_t resp = {.transaction_id = req->transaction_id, - .num_cells_to_activate = 1, - .cells_to_activate[0] = cell}; + .num_cells_to_activate = req->num_cells_available, + .cells_to_activate = calloc_or_fail(req->num_cells_available, sizeof(*resp.cells_to_activate))}; int num = read_version(TO_STRING(NR_RRC_VERSION), &resp.rrc_ver[0], &resp.rrc_ver[1], &resp.rrc_ver[2]); AssertFatal(num == 3, "could not read RRC version string %s\n", TO_STRING(NR_RRC_VERSION)); if (rrc->node_name != NULL) resp.gNB_CU_name = strdup(rrc->node_name); + + if (req->num_cells_available > 1) { + LOG_W(NR_RRC, "Received F1 Setup Request with %u cells, only one cell is supported\n", req->num_cells_available); + } + for (int i = 0; i < req->num_cells_available; i++) { + f1ap_served_cell_info_t *cell_info = &req->cell[i].info; + if (!rrc_gNB_plmn_matches(rrc, cell_info)) { + LOG_E(NR_RRC, + "PLMN mismatch: CU %03d.%0*d, DU %03d%0*d\n", + rrc->configuration.plmn[0].mcc, + rrc->configuration.plmn[0].mnc_digit_length, + rrc->configuration.plmn[0].mnc, + cell_info->plmn.mcc, + cell_info->plmn.mnc_digit_length, + cell_info->plmn.mnc); + fail.cause = F1AP_CauseRadioNetwork_plmn_not_served_by_the_gNB_CU; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + + if (rrc->neighbour_cell_configuration && !valid_du_in_neighbour_configs(rrc->neighbour_cell_configuration, cell_info)) { + LOG_E(NR_RRC, "problem with DU %ld in neighbor configuration, rejecting DU\n", req->gNB_DU_id); + f1ap_setup_failure_t fail = {.cause = F1AP_CauseMisc_unspecified}; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + + const f1ap_gnb_du_system_info_t *sys_info = req->cell[i].sys_info; + NR_MIB_t *mib = NULL; + NR_SIB1_t *sib1 = NULL; + if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) { + if (!extract_sys_info(sys_info, &mib, &sib1)) { + LOG_W(NR_RRC, "rejecting DU ID %ld\n", req->gNB_DU_id); + fail.cause = F1AP_CauseProtocol_semantic_error; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + } + + /* Create cell and add to global tree */ + nr_rrc_cell_container_t *new = calloc_or_fail(1, sizeof(*new)); + new->assoc_id = assoc_id; + cp_f1_served_cell_info_to_cell(new, cell_info); + new->mib = mib; + new->sib1 = sib1; + // Add cell to DU's cell array + nr_rrc_cell_container_t *collision = rrc_add_cell(rrc, new); + if (collision != NULL) { + nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, collision->assoc_id); + const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown"; + LOG_E(NR_RRC, + "Cell ID %lu already exists in DU %s (assoc_id %d), rejecting requesting gNB-DU\n", + new->info.cell_id, + du_name, + collision->assoc_id); + rrc_free_cell_container(new); + fail.cause = F1AP_CauseRadioNetwork_cell_not_available; + rrc->mac_rrc.f1_setup_failure(assoc_id, &fail); + free_f1ap_setup_response(&resp); + return; + } + + nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&du->cells, new); + AssertFatal(added != NULL, "Failed to add cell %ld to DU %ld\n", new->info.cell_id, du->gNB_DU_id); + LOG_I(NR_RRC, "DU %ld: Added cell %ld\n", du->gNB_DU_id, new->info.cell_id); + + served_cells_to_activate_t cell = { + .plmn = cell_info->plmn, + .nr_cellid = cell_info->nr_cellid, + .nrpci = cell_info->nr_pci, + .num_SI = 0, + }; + + // Encode CU SIBs and configure setup response with sysinfo + seq_arr_t *sibs = rrc->SIBs; + if (sibs) { + for (int i = 0; i < sibs->size; i++) { + nr_SIBs_t *sib = (nr_SIBs_t *)seq_arr_at(sibs, i); + switch (sib->SIB_type) { + case 2: { + NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(new->mtc); + sib->SIB_size = do_SIB2_NR(&sib->SIB_buffer, ssbmtc); + cell.SI_msg[cell.num_SI].SI_container = sib->SIB_buffer; + cell.SI_msg[cell.num_SI].SI_container_length = sib->SIB_size; + cell.SI_msg[cell.num_SI].SI_type = sib->SIB_type; + cell.num_SI++; + } break; + default: + AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); + } + } + } + + if (new->mib != NULL &&new->sib1 != NULL) + label_intra_frequency_neighbours(rrc, new); + + resp.cells_to_activate[i] = cell; + } + + LOG_I(NR_RRC, "DU %ld (%s): sending F1 Setup Response\n", req->gNB_DU_id, req->gNB_DU_name); rrc->mac_rrc.f1_setup_response(assoc_id, &resp); free_f1ap_setup_response(&resp); @@ -460,6 +487,10 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) uint32_t ue_id = UE->rrc_ue_id; if (UE->ho_context != NULL) LOG_W(NR_RRC, "DU disconnected while handover for UE %d active\n", ue_id); + + // Remove SCells from this DU for this UE + rrc_remove_ue_scells_from_du(UE, assoc_id); + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); if (ue_data.du_assoc_id != assoc_id) continue; /* this UE is on another DU */ @@ -486,26 +517,77 @@ static int invalidate_du_connections(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) return count; } -static void update_cell_info(nr_rrc_du_container_t *du, const f1ap_served_cell_info_t *new_ci) +/** @brief Update cell information from F1AP DU configuration update + * @param[in] rrc RRC instance (needed to get DU and check PCI duplicates) + * @param[in] old_nci The cell_id used to find the cell in the tree + * @param[in] new_ci Pointer to the new cell information from F1AP message + * @return Pointer to the updated cell container, or NULL on failure + * @note RB tree manipulation: The cell tree is keyed by cell_id. If the cell_id changes + * during update, the node must be removed from the tree before updating (using the + * old cell_id) and re-inserted after updating (using the new cell_id). This is + * necessary because RB trees maintain ordering based on the key value. If cell_id + * doesn't change, no tree manipulation is needed. */ +static nr_rrc_cell_container_t *update_cell_info(gNB_RRC_INST *rrc, const uint64_t old_nci, const f1ap_served_cell_info_t *new_ci) { - DevAssert(du != NULL); + DevAssert(rrc != NULL); DevAssert(new_ci != NULL); - AssertFatal(du->setup_req->num_cells_available == 1, "expected 1 cell for DU, but has %d\n", du->setup_req->num_cells_available); - f1ap_served_cell_info_t *ci = &du->setup_req->cell[0].info; - // make sure no memory is allocated - free_f1ap_cell(ci, NULL); - *ci = copy_f1ap_served_cell_info(new_ci); - - NR_MeasurementTimingConfiguration_t *new_mtc = - extract_mtc(new_ci->measurement_timing_config, new_ci->measurement_timing_config_len); - if (new_mtc != NULL) { - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc); - du->mtc = new_mtc; - } else { - LOG_E(NR_RRC, "error decoding MeasurementTimingConfiguration during cell update, ignoring new config\n"); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, new_mtc); + struct rrc_cell_tree *cells = &rrc->cells; + // Find the cell by old cell_id from the update message + nr_rrc_cell_container_t *cell = get_cell_by_cell_id(cells, old_nci); + if (cell == NULL) { + LOG_W(NR_RRC, "no cell with ID %ld found, ignoring gNB-DU configuration update\n", old_nci); + return NULL; } + + // Check if PCI will change - if so, check for duplicate PCI within the same DU + bool pci_changed = (cell->info.pci != new_ci->nr_pci); + if (pci_changed) { + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, cell->assoc_id); + if (du != NULL) { + // Check if new PCI would conflict with another cell in this DU (excluding current cell) + nr_rrc_cell_container_t *existing_pci = rrc_get_cell_by_pci_for_du(&du->cells, new_ci->nr_pci); + if (existing_pci != NULL && existing_pci != cell) { + LOG_E(NR_RRC, + "Cannot change PCI from %d to %d: PCI %d already exists in this DU (cell ID %lu)\n", + cell->info.pci, + new_ci->nr_pci, + new_ci->nr_pci, + existing_pci->info.cell_id); + return NULL; + } + } + } + + // Check if cell_id will change - if so, need to update tree structure + bool cell_id_changed = (old_nci != new_ci->nr_cellid); + if (cell_id_changed) { + // Check if new cell_id would create a duplicate (excluding current cell) + nr_rrc_cell_container_t *existing = get_cell_by_cell_id(cells, new_ci->nr_cellid); + if (existing != NULL && existing != cell) { + LOG_E(NR_RRC, "Cannot change cell_id from %ld to %ld: new cell_id already exists\n", old_nci, new_ci->nr_cellid); + return NULL; + } + // Remove from tree before updating cell_id + nr_rrc_cell_container_t *removed = RB_REMOVE(rrc_cell_tree, cells, cell); + AssertFatal(removed == cell, "Failed to remove cell %ld from tree\n", old_nci); + } + + // Free old MTC before updating + if (new_ci->measurement_timing_config_len > 0) + ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, cell->mtc); + + // Update cell information from the new F1AP served cell info + cp_f1_served_cell_info_to_cell(cell, new_ci); + + // If cell_id changed, re-insert into tree with new cell_id + if (cell_id_changed) { + nr_rrc_cell_container_t *existing = RB_INSERT(rrc_cell_tree, cells, cell); + AssertFatal(existing == NULL, "Failed to insert cell %ld into tree\n", cell->info.cell_id); + LOG_I(NR_RRC, "Updated cell_id from %ld to %ld\n", old_nci, cell->info.cell_id); + } + + return cell; } void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update_t *conf_up, sctp_assoc_t assoc_id) @@ -522,53 +604,84 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, assoc_id); AssertError(du != NULL, return, "no DU found for assoc_id %d\n", assoc_id); - - const f1ap_served_cell_info_t *info = &du->setup_req->cell[0].info; - if (conf_up->num_cells_to_add > 0) { - // Here we check if the number of cell limit is respectet, otherwise send failure - LOG_W(NR_RRC, "du_configuration_update->cells_to_add_list is not supported yet"); + // Validate each cell to add for uniqueness (cell_id globally; PCI within this DU) + for (uint16_t i = 0; i < conf_up->num_cells_to_add; i++) { + f1ap_served_cell_info_t *cell_info = &conf_up->cell_to_add[i].info; + // Check for duplicate cell_id globally + nr_rrc_cell_container_t *existing = get_cell_by_cell_id(&rrc->cells, cell_info->nr_cellid); + if (existing != NULL) { + nr_rrc_du_container_t *existing_du = get_du_by_assoc_id(rrc, existing->assoc_id); + const char *du_name = existing_du ? existing_du->gNB_DU_name : "unknown"; + LOG_E(NR_RRC, + "Cell ID %lu already exists in DU %s (assoc_id %d), rejecting cell addition\n", + cell_info->nr_cellid, + du_name, + existing->assoc_id); + /** @todo send failure message */ + return; + } + // Check for duplicate PCI within this DU + nr_rrc_cell_container_t *existing_pci = rrc_get_cell_by_pci_for_du(&du->cells, cell_info->nr_pci); + if (existing_pci != NULL) { + LOG_E(NR_RRC, + "PCI %d already exists in this DU (cell ID %lu), rejecting cell addition (cell ID %lu)\n", + cell_info->nr_pci, + existing_pci->info.cell_id, + cell_info->nr_cellid); + /** @todo send failure message */ + return; + } } if (conf_up->num_cells_to_modify > 0) { // here the old nrcgi is used to find the cell information, if it exist then we modify consequently otherwise we fail AssertFatal(conf_up->num_cells_to_modify == 1, "cannot handle more than one cell!\n"); - - if (info->nr_cellid != conf_up->cell_to_modify[0].old_nr_cellid) { - LOG_W(NR_RRC, "no cell with ID %ld found, ignoring gNB-DU configuration update\n", conf_up->cell_to_modify[0].old_nr_cellid); - return; - } + const uint64_t old_nci = conf_up->cell_to_modify[0].old_nr_cellid; + const f1ap_served_cell_info_t *new_ci = &conf_up->cell_to_modify[0].info; // verify the new plmn of the cell - if (!rrc_gNB_plmn_matches(rrc, &conf_up->cell_to_modify[0].info)) { + if (!rrc_gNB_plmn_matches(rrc, new_ci)) { LOG_W(NR_RRC, "PLMN does not match, ignoring gNB-DU configuration update\n"); return; } - update_cell_info(du, &conf_up->cell_to_modify[0].info); + nr_rrc_cell_container_t *cell = update_cell_info(rrc, old_nci, new_ci); + if (cell == NULL) { + LOG_W(NR_RRC, "Failed to update cell %ld, ignoring gNB-DU configuration update\n", old_nci); + return; + } + if (cell->assoc_id != du->assoc_id) { + LOG_W(NR_RRC, + "cell %ld belongs to different DU (assoc_id %d vs %d), ignoring gNB-DU configuration update\n", + old_nci, + cell->assoc_id, + du->assoc_id); + return; + } const f1ap_gnb_du_system_info_t *sys_info = conf_up->cell_to_modify[0].sys_info; if (sys_info != NULL && sys_info->mib != NULL && !(sys_info->sib1 == NULL && IS_SA_MODE(get_softmodem_params()))) { // MIB is mandatory, so will be overwritten. SIB1 is optional, so will // only be overwritten if present in sys_info - ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib); + ASN_STRUCT_FREE(asn_DEF_NR_MIB, cell->mib); if (sys_info->sib1 != NULL) { - ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1); - du->sib1 = NULL; + ASN_STRUCT_FREE(asn_DEF_NR_SIB1, cell->sib1); + cell->sib1 = NULL; } NR_MIB_t *mib = NULL; - if (!extract_sys_info(sys_info, &mib, &du->sib1)) { - LOG_W(NR_RRC, "cannot update sys_info for DU %ld\n", du->setup_req->gNB_DU_id); + if (!extract_sys_info(sys_info, &mib, &cell->sib1)) { + LOG_W(NR_RRC, "cannot update sys_info for DU %ld\n", du->gNB_DU_id); } else { DevAssert(mib != NULL); - du->mib = mib; - LOG_I(NR_RRC, "update system information of DU %ld\n", du->setup_req->gNB_DU_id); + cell->mib = mib; + LOG_I(NR_RRC, "update system information of DU %ld\n", du->gNB_DU_id); } } - if (du->mib != NULL && du->sib1 != NULL) { - const f1ap_served_cell_info_t *cell_info = &du->setup_req->cell[0].info; - label_intra_frequency_neighbours(rrc, du, cell_info); + + if (cell && cell->mib != NULL && cell->sib1 != NULL && cell->assoc_id == du->assoc_id) { + label_intra_frequency_neighbours(rrc, cell); } } @@ -601,18 +714,8 @@ void rrc_CU_process_f1_lost_connection(gNB_RRC_INST *rrc, f1ap_lost_connection_t return; } - f1ap_setup_req_t *req = du->setup_req; - LOG_I(NR_RRC, "releasing DU ID %ld (%s) on assoc_id %d\n", req->gNB_DU_id, req->gNB_DU_name, assoc_id); - ASN_STRUCT_FREE(asn_DEF_NR_MIB, du->mib); - ASN_STRUCT_FREE(asn_DEF_NR_SIB1, du->sib1); - ASN_STRUCT_FREE(asn_DEF_NR_MeasurementTimingConfiguration, du->mtc); - if (du->setup_req) - free_f1ap_setup_request(du->setup_req); - free(du->setup_req); - nr_rrc_du_container_t *removed = RB_REMOVE(rrc_du_tree, &rrc->dus, du); - DevAssert(removed != NULL); - free(du); - rrc->num_dus--; + // Clean up DU and associated cell resources + rrc_cleanup_du(rrc, du); int num = invalidate_du_connections(rrc, assoc_id); if (num > 0) { @@ -628,52 +731,53 @@ nr_rrc_du_container_t *get_du_for_ue(gNB_RRC_INST *rrc, uint32_t ue_id) return get_du_by_assoc_id(rrc, ue_data.du_assoc_id); } -nr_rrc_du_container_t *get_du_by_assoc_id(gNB_RRC_INST *rrc, sctp_assoc_t assoc_id) -{ - nr_rrc_du_container_t e = {.assoc_id = assoc_id}; - return RB_FIND(rrc_du_tree, &rrc->dus, &e); -} - -/* \brief find DU by cell ID. Note: currently the CU is limited to one cell per - * DU, hence here, DU == cell. Modify this to look up a specific cell. */ -nr_rrc_du_container_t *get_du_by_cell_id(gNB_RRC_INST *rrc, uint64_t cell_id) -{ - nr_rrc_du_container_t *du = NULL; - RB_FOREACH(du, rrc_du_tree, &rrc->dus) { - if (cell_id == du->setup_req->cell[0].info.nr_cellid) - return du; - } - return NULL; -} - +/** @brief Dump DU and cell information to file + * @param[in] rrc RRC instance + * @param[in] f File handle to write to + * @note Complexity: O(d + N_CELL) = O(N_CELL) where d = number of DUs, N_CELL = total number of cells across all DUs + * @note Called periodically (every 1 second) for stats logging */ void dump_du_info(const gNB_RRC_INST *rrc, FILE *f) { fprintf(f, "%ld connected DUs \n", rrc->num_dus); - int i = 1; + int du_idx = 1; nr_rrc_du_container_t *du = NULL; - /* cast is necessary to eliminate warning "discards ‘const’ qualifier" */ + /* cast is necessary to eliminate warning "discards 'const' qualifier" */ RB_FOREACH(du, rrc_du_tree, &((gNB_RRC_INST *)rrc)->dus) { - const f1ap_setup_req_t *sr = du->setup_req; - fprintf(f, "[%d] DU ID %ld (%s) ", i++, sr->gNB_DU_id, sr->gNB_DU_name); + fprintf(f, "[%d] DU ID %ld (%s) ", du_idx++, du->gNB_DU_id, du->gNB_DU_name); if (du->assoc_id == -1) { fprintf(f, "integrated DU-CU"); } else { fprintf(f, "assoc_id %d", du->assoc_id); } - const f1ap_served_cell_info_t *info = &sr->cell[0].info; - fprintf(f, ": nrCellID %ld, PCI %d, SSB ARFCN %d\n", info->nr_cellid, info->nr_pci, get_ssb_arfcn(du)); - if (info->mode == F1AP_MODE_TDD) { - const f1ap_nr_frequency_info_t *fi = &info->tdd.freqinfo; - const f1ap_transmission_bandwidth_t *tb = &info->tdd.tbw; - fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", fi->band, fi->arfcn, 15 * (1 << tb->scs), tb->nrb); + /* Print all cells for this DU */ + int cell_count = seq_arr_size(&du->cells); + + if (cell_count == 0) { + fprintf(f, ": no cells\n"); } else { - const f1ap_nr_frequency_info_t *dfi = &info->fdd.dl_freqinfo; - const f1ap_transmission_bandwidth_t *dtb = &info->fdd.dl_tbw; - fprintf(f, " FDD: DL band %d ARFCN %d SCS %d (kHz) PRB %d\n", dfi->band, dfi->arfcn, 15 * (1 << dtb->scs), dtb->nrb); - const f1ap_nr_frequency_info_t *ufi = &info->fdd.ul_freqinfo; - const f1ap_transmission_bandwidth_t *utb = &info->fdd.ul_tbw; - fprintf(f, " UL band %d ARFCN %d SCS %d (kHz) PRB %d\n", ufi->band, ufi->arfcn, 15 * (1 << utb->scs), utb->nrb); + fprintf(f, ": %d cell%s\n", cell_count, cell_count > 1 ? "s" : ""); + int cell_idx = 1; + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell_ptr, &du->cells) { + nr_rrc_cell_container_t *cell = *cell_ptr; + const char *mode = (cell->info.mode == NR_MODE_TDD) ? "TDD" : "FDD"; + fprintf(f, + " [%d] nrCellID %lu, PCI %d, Mode %s, SSB ARFCN %d\n", + cell_idx++, + cell->info.cell_id, + cell->info.pci, + mode, + ssb_arfcn_mtc(cell->mtc)); + if (cell->info.mode == NR_MODE_TDD) { + const nr_rrc_freq_info_t *tdd = &cell->info.tdd.dlul; + fprintf(f, " TDD: band %d ARFCN %d SCS %d (kHz) PRB %d\n", tdd->band, tdd->arfcn, 15 * (1 << tdd->scs), tdd->nrb); + } else { + const nr_rrc_freq_info_t *dl = &cell->info.fdd.dl; + const nr_rrc_freq_info_t *ul = &cell->info.fdd.ul; + fprintf(f, " DL: band %d ARFCN %d SCS %d (kHz) PRB %d\n", dl->band, dl->arfcn, 15 * (1 << dl->scs), dl->nrb); + fprintf(f, " UL: band %d ARFCN %d SCS %d (kHz) PRB %d\n", ul->band, ul->arfcn, 15 * (1 << ul->scs), ul->nrb); + } + } } } } diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index 514416211f..8942af9af5 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -30,6 +30,7 @@ struct f1ap_setup_req_s; struct f1ap_lost_connection_t; struct gNB_RRC_INST_s; struct nr_rrc_du_container_t; +struct nr_rrc_cell_container_t; struct f1ap_gnb_du_configuration_update_s; struct f1ap_served_cell_info_t; @@ -38,15 +39,10 @@ void rrc_CU_process_f1_lost_connection(struct gNB_RRC_INST_s *rrc, struct f1ap_l void rrc_gNB_process_f1_du_configuration_update(struct f1ap_gnb_du_configuration_update_s *conf_up, sctp_assoc_t assoc_id); struct nr_rrc_du_container_t *get_du_for_ue(struct gNB_RRC_INST_s *rrc, uint32_t ue_id); -struct nr_rrc_du_container_t *get_du_by_assoc_id(struct gNB_RRC_INST_s *rrc, sctp_assoc_t assoc_id); -const struct f1ap_served_cell_info_t *get_cell_information_by_phycellId(int phyCellId); -struct nr_rrc_du_container_t *get_du_by_cell_id(struct gNB_RRC_INST_s *rrc, uint64_t cell_id); void dump_du_info(const struct gNB_RRC_INST_s *rrc, FILE *f); -int get_dl_band(const struct f1ap_served_cell_info_t *cell_info); -int get_ssb_scs(const struct f1ap_served_cell_info_t *cell_info); -int get_ssb_arfcn(const struct nr_rrc_du_container_t *du); +int get_ssb_arfcn(const struct nr_rrc_cell_container_t *cell); struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_assoc_t source_assoc_id); diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index a2230c1146..d18937fbc5 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -27,6 +27,7 @@ #include "nr_rrc_proto.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rrc_gNB_radio_bearers.h" #include "rrc_gNB_UE_context.h" #include "openair2/LAYER2/NR_MAC_COMMON/nr_mac.h" @@ -37,6 +38,7 @@ #include "openair3/SECU/key_nas_deriver.h" #include "openair2/RRC/NR/rrc_gNB_NGAP.h" #include "NR_DL-DCCH-MessageType.h" +#include "rrc_cell_management.h" #ifdef E2_AGENT #include "openair2/E2AP/RAN_FUNCTION/O-RAN/ran_func_rc_extern.h" @@ -80,15 +82,12 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) /* update F1 data: secondary UE association and DU association */ ue_data.secondary_ue = target_ctx->du_ue_id; - ue_data.du_assoc_id = target_ctx->du->assoc_id; + ue_data.du_assoc_id = target_ctx->cell->assoc_id; bool success = cu_update_f1_ue_data(UE->rrc_ue_id, &ue_data); DevAssert(success); /* update UE RNTI */ UE->rnti = target_ctx->new_rnti; - - /* update UE NR cell ID */ - UE->nr_cellid = target_ctx->du->setup_req->cell[0].info.nr_cellid; } /* \brief Initiate a handover of UE to a specific target cell handled by this @@ -101,8 +100,7 @@ void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE) * \param ho_ctxt contextual data for the type of handover (F1, N2, Xn) */ static void nr_initiate_handover(const gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, - const nr_rrc_du_container_t *source_du, - const nr_rrc_du_container_t *target_du, + const nr_rrc_cell_container_t *source_cell, byte_array_t *ho_prep_info, ho_req_ack_t ack, ho_success_t success, @@ -111,10 +109,10 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, { DevAssert(rrc != NULL); DevAssert(ue != NULL); - DevAssert(target_du != NULL); - // source_du might be NULL -> inter-CU handover + // source_cell might be NULL -> inter-CU handover DevAssert(ho_prep_info->buf != NULL && ho_prep_info->len > 0); DevAssert(ue->ho_context); + DevAssert(ue->ho_context->target != NULL); // if any reconfiguration is ongoing, abort handover request for (int i = 0; i < NR_RRC_TRANSACTION_IDENTIFIER_NUMBER; ++i) { @@ -125,7 +123,9 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } nr_handover_context_t *ho_ctx = ue->ho_context; - ho_ctx->target->du = target_du; + const nr_rrc_cell_container_t *target_cell = ho_ctx->target->cell; + DevAssert(target_cell != NULL); + // we will know target->{du_ue_id,new_rnti} once we have UE ctxt setup // response ho_ctx->target->ho_req_ack = ack; @@ -133,14 +133,14 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, ho_ctx->target->ho_failure = failure; const f1_ue_data_t ue_data = cu_get_f1_ue_data(ue->rrc_ue_id); - if (source_du != NULL) { - DevAssert(source_du->assoc_id == ue_data.du_assoc_id); - // we also have the source DU (F1 handover), store meta info + if (source_cell != NULL) { + DevAssert(source_cell->assoc_id == ue_data.du_assoc_id); ho_ctx->source->ho_cancel = cancel; - ho_ctx->source->du = source_du; + ho_ctx->source->cell = source_cell; ho_ctx->source->du_ue_id = ue_data.secondary_ue; ho_ctx->source->old_rnti = ue->rnti; + DevAssert(source_cell->assoc_id == ue_data.du_assoc_id); // Save the GTP-U tunnel info for source DU before process UE context setup request/response // since tunnel info will be updated by calling store_du_f1u_tunnel() in rrc_CU_process_ue_context_setup_response() @@ -153,14 +153,14 @@ static void nr_initiate_handover(const gNB_RRC_INST *rrc, } LOG_A(NR_RRC, - "Handover triggered for UE %u/RNTI %04x towards DU %ld/assoc_id %d/PCI %d\n", + "Handover triggered for UE %u/RNTI %04x towards cell %ld/assoc_id %d/PCI %d\n", ue->rrc_ue_id, ue->rnti, - target_du->setup_req->gNB_DU_id, - target_du->assoc_id, - target_du->setup_req->cell[0].info.nr_pci); + target_cell->info.cell_id, + target_cell->assoc_id, + target_cell->info.pci); - rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_du, ho_prep_info); + rrc_f1_ue_context_setup_for_target_du(rrc, ue, target_cell, ho_prep_info); } typedef struct deliver_ue_ctxt_modification_data_t { @@ -249,15 +249,19 @@ static void nr_rrc_f1_ho_complete(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) DevAssert(UE->ho_context != NULL); nr_ho_source_cu_t *source_ctx = UE->ho_context->source; DevAssert(source_ctx != NULL); - RETURN_IF_INVALID_ASSOC_ID(source_ctx->du->assoc_id); + RETURN_IF_INVALID_ASSOC_ID(source_ctx->cell->assoc_id); f1ap_ue_context_rel_cmd_t cmd = { .gNB_CU_ue_id = UE->rrc_ue_id, .gNB_DU_ue_id = source_ctx->du_ue_id, .cause = F1AP_CAUSE_RADIO_NETWORK, .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure }; - rrc->mac_rrc.ue_context_release_command(source_ctx->du->assoc_id, &cmd); - LOG_I(NR_RRC, "UE %d Handover: trigger release on DU assoc_id %d\n", UE->rrc_ue_id, source_ctx->du->assoc_id); + rrc->mac_rrc.ue_context_release_command(source_ctx->cell->assoc_id, &cmd); + LOG_I(NR_RRC, + "UE %d Handover: trigger release on cell PCI %d/assoc_id %d\n", + UE->rrc_ue_id, + source_ctx->cell->info.pci, + source_ctx->cell->assoc_id); } static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) @@ -271,10 +275,13 @@ static void nr_rrc_cancel_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) .cause = F1AP_CAUSE_RADIO_NETWORK, // better .cause_value = 5, // 5 = F1AP_CauseRadioNetwork_interaction_with_other_procedure }; - rrc->mac_rrc.ue_context_release_command(target_ctx->du->assoc_id, &cmd); + rrc->mac_rrc.ue_context_release_command(target_ctx->cell->assoc_id, &cmd); } -void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du) +void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_cell_container_t *source_cell, + const nr_rrc_cell_container_t *target_cell) { DevAssert(rrc != NULL); DevAssert(ue != NULL); @@ -288,6 +295,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain return; } ue->ho_context = alloc_ho_ctx(HO_CTX_BOTH); + ue->ho_context->target->cell = target_cell; // corresponds to a "handover request", 38.300 Sec 9.3.2.3 // see also 38.413 Sec 9.3.1.29 for information on source-CU to target-CU @@ -299,7 +307,7 @@ void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_contain ho_success_t success = nr_rrc_f1_ho_complete; ho_cancel_t cancel = nr_rrc_cancel_f1_ho; byte_array_t hpi = {.buf = buf, .len = size}; - nr_initiate_handover(rrc, ue, source_du, target_du, &hpi, ack, success, cancel, NULL); + nr_initiate_handover(rrc, ue, source_cell, &hpi, ack, success, cancel, NULL); } void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue) @@ -324,6 +332,11 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) LOG_E(NR_RRC, "cannot get source gNB-DU with assoc_id %d for UE %u\n", ue_data.du_assoc_id, ue->rrc_ue_id); return; } + nr_rrc_cell_container_t *source_cell = rrc_get_pcell_for_ue(rrc, ue); + if (source_cell == NULL) { + LOG_E(NR_RRC, "cannot get source cell for UE %u\n", ue->rrc_ue_id); + return; + } nr_rrc_du_container_t *target_du = find_target_du(rrc, source_du->assoc_id); if (target_du == NULL) { @@ -331,7 +344,19 @@ void nr_HO_F1_trigger_telnet(gNB_RRC_INST *rrc, uint32_t rrc_ue_id) return; } - nr_rrc_trigger_f1_ho(rrc, ue, source_du, target_du); + // For target cell, get the first cell from target DU + // (in future, this could be selected based on measurement) + nr_rrc_cell_container_t *target_cell = NULL; + FOR_EACH_SEQ_ARR (nr_rrc_cell_container_t **, cell_ptr, &target_du->cells) { + target_cell = *cell_ptr; + break; // Get first cell + } + if (target_cell == NULL) { + LOG_E(NR_RRC, "cannot get target cell for UE %u\n", ue->rrc_ue_id); + return; + } + + nr_rrc_trigger_f1_ho(rrc, ue, source_cell, target_cell); } /** @brief Generate the HandoverPreparationInformation to be carried @@ -395,6 +420,74 @@ static byte_array_t rrc_gNB_encode_HandoverCommand(gNB_RRC_UE_t *UE, gNB_RRC_INS return out; } +/** @brief Update cell association after handover: set PCell on target DU + * @param[in] rrc RRC instance + * @param[in] UE UE context + * @return true on success, false on failure + * + * Generic for all handover types (F1, N2, Xn). Behaviour depends on whether + * ho_context->source is set on this CU: + * - Source present (intra-CU): F1 HO, remove all serving cells belonging to + * the source DU, then add target cell as PCell. + * - Source absent (inter-CU): N2 or Xn handover (target CU created new UE + * on Handover Request). This UE context has no serving cells yet; add + * target cell as first PCell. */ +bool nr_rrc_update_cell_assoc_after_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) +{ + DevAssert(UE->ho_context); + DevAssert(UE->ho_context->target); + nr_ho_target_cu_t *target_ctx = UE->ho_context->target; + + /* Pre-check: ensure F1 UE data matches target cell before mutating state. */ + if (!cu_exists_f1_ue_data(UE->rrc_ue_id)) { + LOG_E(NR_RRC, "UE %d handover: no F1 UE data, cannot add PCell\n", UE->rrc_ue_id); + return false; + } + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + if (ue_data.du_assoc_id != target_ctx->cell->assoc_id) { + LOG_E(NR_RRC, + "UE %d handover: target cell assoc_id %d does not match F1 du_assoc_id %d\n", + UE->rrc_ue_id, + target_ctx->cell->assoc_id, + ue_data.du_assoc_id); + return false; + } + + if (UE->ho_context->source) { + /* F1 HO (intra-CU): migrate serving cells from source DU to target cell */ + nr_ho_source_cu_t *source_ctx = UE->ho_context->source; + + // Remove SCells that belong to the source DU during handover (cell group changes) + // This also removes PCell if it was on the source DU + rrc_remove_ue_scells_from_du(UE, source_ctx->cell->assoc_id); + + // Update PCell in serving_cells + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, + "UE %d handover: failed to add PCell (cell %ld) to serving cells\n", + UE->rrc_ue_id, + target_ctx->cell->info.cell_id); + return false; + } + } else { + /* Inter-CU (N2/Xn target): this UE context has no serving cells yet */ + const ue_serving_cell_t *existing_pcell = ue_get_pcell_entry(UE); + AssertFatal(existing_pcell == NULL, "UE context should not have PCell yet (inter-CU target)\n"); + + ue_serving_cell_t *added = rrc_add_ue_serving_cell(UE, target_ctx->cell, RRC_PCELL_INDEX); + if (added == NULL) { + LOG_E(NR_RRC, + "UE %d handover: failed to add PCell (cell %ld) to serving cells\n", + UE->rrc_ue_id, + target_ctx->cell->info.cell_id); + return false; + } + } + + return true; +} + /** @brief This callback is used by the target gNB * to trigger the Handover Request Acknowledge towards the AMF */ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) @@ -407,13 +500,23 @@ static void nr_rrc_n2_ho_acknowledge(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE) AssertFatal(previous_data.secondary_ue == -1, "there was already a DU present\n"); nr_rrc_apply_target_context(UE); + /* Update cell association after handover */ + nr_ho_target_cu_t *target = UE->ho_context->target; + if (!nr_rrc_update_cell_assoc_after_ho(rrc, UE)) { + ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, + .cause.type = NGAP_CAUSE_RADIO_NETWORK, + .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; + target->ho_failure(rrc, UE->rrc_ue_id, &fail); + return; + } + byte_array_t hoCommand = rrc_gNB_encode_HandoverCommand(UE, rrc); if (hoCommand.len < 0) { LOG_E(NR_RRC, "ASN1 message encoding failed: failed to generate Handover Command Message\n"); ngap_handover_failure_t fail = {.amf_ue_ngap_id = UE->amf_ue_ngap_id, .cause.type = NGAP_CAUSE_RADIO_NETWORK, .cause.value = NGAP_CAUSE_RADIO_NETWORK_HO_FAILURE_IN_TARGET_5GC_NGRAN_NODE_OR_TARGET_SYSTEM}; - UE->ho_context->target->ho_failure(rrc, UE->rrc_ue_id, &fail); + target->ho_failure(rrc, UE->rrc_ue_id, &fail); return; } else { LOG_D(NR_RRC, "HO LOG: Handover Command for UE %u Encoded (%ld bytes)\n", UE->rrc_ue_id, hoCommand.len); @@ -463,8 +566,7 @@ void nr_rrc_trigger_n2_ho_target(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue) ho_success_t success = nr_rrc_n2_ho_complete; ho_failure_t failure = nr_rrc_n2_ho_failure; - const nr_rrc_du_container_t *target_du = get_du_for_ue(rrc, ue->rrc_ue_id); - nr_initiate_handover(rrc, ue, NULL, target_du, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); + nr_initiate_handover(rrc, ue, NULL, &ue->ho_context->target->ue_ho_prep_info, ack, success, NULL, failure); FREE_AND_ZERO_BYTE_ARRAY(ue->ho_context->target->ue_ho_prep_info); NR_UE_NR_Capability_t *ue_cap = get_ue_nr_capability(ue->rnti, ue->ue_cap_buffer.buf, ue->ue_cap_buffer.len); @@ -495,7 +597,13 @@ void nr_rrc_trigger_n2_ho(gNB_RRC_INST *rrc, } ue->ho_context = alloc_ho_ctx(HO_CTX_SOURCE); - ue->ho_context->source->du = get_du_for_ue(rrc, ue->rrc_ue_id); + nr_rrc_cell_container_t *source_cell = rrc_get_pcell_for_ue(rrc, ue); + if (source_cell == NULL) { + LOG_E(NR_RRC, "No source cell found for UE %d\n", ue->rrc_ue_id); + free_byte_array(hoPrepInfo); + return; + } + ue->ho_context->source->cell = source_cell; ue->ho_context->source->ho_status_transfer = rrc_gNB_send_NGAP_ul_ran_status_transfer; ue->ho_context->source->ho_cancel = nr_rrc_n2_ho_cancel; @@ -514,42 +622,51 @@ void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t return; } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; - - struct nr_rrc_du_container_t *du = get_du_for_ue(rrc, rrc_ue_id); + nr_rrc_du_container_t *du = get_du_for_ue(rrc, UE->rrc_ue_id); if (du == NULL) { LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown DU\n", rrc_ue_id); return; } - uint16_t scell_pci = du->setup_req->cell[0].info.nr_pci; + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(rrc, UE); + if (pcell == NULL) { + LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: Unknown cell\n", rrc_ue_id); + return; + } + uint16_t scell_pci = pcell->info.pci; // Simulate handover on the same cell (testing purposes) if (neighbour_pci == scell_pci) { LOG_I(NR_RRC, "UE %d: trigger handover on the same cell PCI=%d\n", rrc_ue_id, neighbour_pci); nr_neighbour_cell_t neighbourConfig = { .isIntraFrequencyNeighbour = true, - .gNB_ID = du->setup_req->gNB_DU_id, - .nrcell_id = du->setup_req->cell[0].info.nr_cellid, - .physicalCellId = du->setup_req->cell[0].info.nr_pci, - .plmn = du->setup_req->cell[0].info.plmn, - .subcarrierSpacing = du->setup_req->cell[0].info.tdd.tbw.scs, + .gNB_ID = du->gNB_DU_id, + .nrcell_id = pcell->info.cell_id, + .physicalCellId = pcell->info.pci, + .plmn = pcell->info.plmn, + .subcarrierSpacing = pcell->info.mode == NR_MODE_TDD ? pcell->info.tdd.dlul.scs : pcell->info.fdd.dl.scs, }; nr_rrc_trigger_n2_ho(rrc, UE, neighbour_pci, &neighbourConfig); return; } - const f1ap_served_cell_info_t *scell_du = get_cell_information_by_phycellId(scell_pci); - DevAssert(scell_du); LOG_I(NR_RRC, "UE %d: triggered N2 HO, source PCI=%d to target PCI=%d\n", rrc_ue_id, scell_pci, neighbour_pci); - const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, scell_du->nr_cellid); - const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); + const neighbour_cell_configuration_t *cell = get_neighbour_cell_config(rrc, pcell->info.cell_id); + if (cell == NULL) { + LOG_E(NR_RRC, + "N2 HO trigger failed for UE %d: could not find neighbour cell with Cell ID=%ld\n", + rrc_ue_id, + pcell->info.cell_id); + return; + } + const nr_neighbour_cell_t *neighbour = get_neighbour_cell_by_pci(cell, neighbour_pci); if (neighbour == NULL) { LOG_E(NR_RRC, "N2 HO trigger failed for UE %d: could not find neighbour cell with PCI=%d\n", rrc_ue_id, neighbour_pci); return; } - nr_rrc_trigger_n2_ho(rrc, UE, scell_du->nr_pci, neighbour); + nr_rrc_trigger_n2_ho(rrc, UE, pcell->info.pci, neighbour); } // This function detects if there are at least two different ssbFrequency values, and if so, returns meas_timing_config; diff --git a/openair2/RRC/NR/rrc_gNB_mobility.h b/openair2/RRC/NR/rrc_gNB_mobility.h index 456730060e..d4a51efeb9 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.h +++ b/openair2/RRC/NR/rrc_gNB_mobility.h @@ -28,7 +28,7 @@ /* forward declarations */ typedef struct gNB_RRC_INST_s gNB_RRC_INST; typedef struct gNB_RRC_UE_s gNB_RRC_UE_t; -typedef struct nr_rrc_du_container_t nr_rrc_du_container_t; +typedef struct nr_rrc_cell_container_t nr_rrc_cell_container_t; typedef struct NR_CellGroupConfig NR_CellGroupConfig_t; @@ -40,8 +40,8 @@ typedef int (*ho_status_transfer_t)(gNB_RRC_INST *rrc, const e1_pdcp_status_info_t *pdcp_status); typedef struct nr_ho_source_cu { - /// pointer to the (source) DU structure - const nr_rrc_du_container_t *du; + /// pointer to the (source) cell container + const nr_rrc_cell_container_t *cell; /// (source) DU UE ID; in F1, the DU UE ID will change to the new (target) DU /// UE ID during handover, and the CU needs to keep track of this in case of /// reestablishment @@ -67,8 +67,8 @@ typedef void (*ho_failure_t)(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handove typedef void (*ho_trigger_t)(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue); typedef struct nr_ho_target_cu { - /// pointer to the (target) DU structure - const nr_rrc_du_container_t *du; + /// pointer to the (target) cell container + const nr_rrc_cell_container_t *cell; /// (target) DU UE ID; the (source) DU UE ID will change to the new (target) /// DU UE ID after sending a reconfiguration, which is later than when /// receiving this ID @@ -95,7 +95,10 @@ typedef struct nr_handover_context_s { typedef enum { HO_CTX_BOTH, HO_CTX_SOURCE, HO_CTX_TARGET } ho_ctx_type_t; nr_handover_context_t *alloc_ho_ctx(ho_ctx_type_t type); -void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *ue, nr_rrc_du_container_t *source_du, nr_rrc_du_container_t *target_du); +void nr_rrc_trigger_f1_ho(gNB_RRC_INST *rrc, + gNB_RRC_UE_t *ue, + const nr_rrc_cell_container_t *source_cell, + const nr_rrc_cell_container_t *target_cell); void nr_rrc_finalize_ho(gNB_RRC_UE_t *ue); void nr_rrc_n2_ho_failure(gNB_RRC_INST *rrc, uint32_t gnb_ue_id, ngap_handover_failure_t *msg); @@ -112,4 +115,6 @@ byte_array_t *get_meas_timing_config(const NR_MeasurementTimingConfiguration_t * void nr_rrc_apply_target_context(gNB_RRC_UE_t *UE); +bool nr_rrc_update_cell_assoc_after_ho(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE); + #endif /* RRC_GNB_MOBILITY_H_ */ diff --git a/openair2/RRC/NR/rrc_gNB_nsa.c b/openair2/RRC/NR/rrc_gNB_nsa.c index 88f06338d7..18513d14c5 100644 --- a/openair2/RRC/NR/rrc_gNB_nsa.c +++ b/openair2/RRC/NR/rrc_gNB_nsa.c @@ -62,6 +62,7 @@ #include "openair3/SECU/key_nas_deriver.h" #include "openair2/SDAP/nr_sdap/nr_sdap_entity.h" #include "rrc_gNB_du.h" +#include "rrc_cell_management.h" #include "rlc.h" #include "s1ap_messages_types.h" #include "tree.h" @@ -368,20 +369,37 @@ void rrc_add_nsa_user(gNB_RRC_INST *rrc, x2ap_ENDC_sgnb_addition_req_t *m, sctp_ *ue_agg_mbr_ul = 1000000000; byte_array_t *cg_configinfo = malloc_or_fail(sizeof(*cg_configinfo)); *cg_configinfo = cgci; + + f1_ue_data_t ue_data = cu_get_f1_ue_data(UE->rrc_ue_id); + RETURN_IF_INVALID_ASSOC_ID(ue_data.du_assoc_id); + nr_rrc_du_container_t *du = get_du_by_assoc_id(rrc, ue_data.du_assoc_id); + if (!du) { + LOG_E(NR_RRC, "UE %d: no valid cell for UE context setup (DU assoc_id %d not found)\n", UE->rrc_ue_id, ue_data.du_assoc_id); + return; + } + // Get the first cell from the DU (NSA assumption: 1 cell per DU) + if (seq_arr_size(&du->cells) == 0) { + LOG_E(NR_RRC, "UE %d: no cells available in DU (assoc_id %d)\n", UE->rrc_ue_id, ue_data.du_assoc_id); + return; + } + nr_rrc_cell_container_t *ue_cell = *(nr_rrc_cell_container_t **)seq_arr_at(&du->cells, 0); + // Add the cell as PCell + if (rrc_add_ue_serving_cell(UE, ue_cell, RRC_PCELL_INDEX) == NULL) { + LOG_E(NR_RRC, "UE %d: failed to add PCell (cell %ld) for UE context setup\n", UE->rrc_ue_id, ue_cell->info.cell_id); + return; + } f1ap_ue_context_setup_req_t req = { .gNB_CU_ue_id = UE->rrc_ue_id, .plmn.mcc = rrc->configuration.plmn[0].mcc, .plmn.mnc = rrc->configuration.plmn[0].mnc, .plmn.mnc_digit_length = rrc->configuration.plmn[0].mnc_digit_length, - .nr_cellid = rrc->nr_cellid, - .servCellIndex = 0, + .nr_cellid = ue_cell->info.cell_id, + .servCellIndex = RRC_PCELL_INDEX, .drbs_len = 1, .drbs = drb, .cu_to_du_rrc_info.cg_configinfo = cg_configinfo, .gnb_du_ue_agg_mbr_ul = ue_agg_mbr_ul, }; - 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); rrc->mac_rrc.ue_context_setup_request(ue_data.du_assoc_id, &req); free_ue_context_setup_req(&req); } diff --git a/openair2/RRC/NR/tests/CMakeLists.txt b/openair2/RRC/NR/tests/CMakeLists.txt index f7a4a95b1b..f68501c99e 100644 --- a/openair2/RRC/NR/tests/CMakeLists.txt +++ b/openair2/RRC/NR/tests/CMakeLists.txt @@ -5,3 +5,19 @@ add_test(NAME rrc_bearers_test COMMAND rrc_bearers_test) target_include_directories(rrc_bearers_test PRIVATE ./common/utils/ds common/utils/LOG) target_link_libraries(rrc_bearers_test PRIVATE ds alg asn1_e1ap asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs LOG T) target_compile_definitions(rrc_bearers_test PRIVATE ENABLE_TESTS) + +add_executable(rrc_cell_management_test rrc_cell_management_test.c) +add_dependencies(tests rrc_cell_management_test) +add_test(NAME rrc_cell_management_test COMMAND rrc_cell_management_test) +target_include_directories(rrc_cell_management_test PRIVATE ./common/utils/ds common/utils/LOG) +target_link_libraries(rrc_cell_management_test PRIVATE + rrc_cell_management + f1ap + ds + alg + LOG + utils + asn1_nr_rrc_hdrs + asn1_lte_rrc_hdrs +) +target_compile_definitions(rrc_cell_management_test PRIVATE ENABLE_TESTS) diff --git a/openair2/RRC/NR/tests/rrc_cell_management_test.c b/openair2/RRC/NR/tests/rrc_cell_management_test.c new file mode 100644 index 0000000000..b3aa5e6881 --- /dev/null +++ b/openair2/RRC/NR/tests/rrc_cell_management_test.c @@ -0,0 +1,460 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include +#include +#include +#include "common/utils/utils.h" // for calloc_or_fail +#include "common/utils/assertions.h" // for AssertFatal, DevAssert +#include "common/utils/collection/tree.h" // for RB tree macros +#include "openair2/RRC/NR/rrc_cell_management.h" // for cell management functions +#include "openair2/RRC/NR/rrc_gNB_du.h" // for get_du_by_assoc_id +#include "common/utils/LOG/log.h" // for LOG_I, LOG_A, etc. +#include "common/utils/ds/seq_arr.h" // for seq_arr_free +#include "openair2/F1AP/f1ap_ids.h" // for cu_init_f1_ue_data, cu_add_f1_ue_data + +// Test constants +static const sctp_assoc_t ASSOC_ID_DU1 = 8; +static const sctp_assoc_t ASSOC_ID_DU2 = 9; +static const sctp_assoc_t ASSOC_ID_DU3 = 10; +static const uint64_t DU_ID_1 = 1001; +static const uint64_t DU_ID_2 = 1002; +static const uint64_t DU_ID_3 = 1003; +static const uint64_t CELL_ID_1 = 12345678; +static const uint64_t CELL_ID_2 = 87654321; +static const uint64_t CELL_ID_3 = 11111111; +static const uint64_t CELL_ID_4 = 22222222; +static const uint64_t CELL_ID_5 = 33333333; +static const uint64_t CELL_ID_6 = 44444444; +static const uint64_t CELL_ID_7 = 55555555; +static const uint64_t CELL_ID_NONEXISTENT = 99999999; +static const uint16_t PCI_1 = 1; +static const uint16_t PCI_2 = 4; +static const uint16_t PCI_3 = 5; +static const uint16_t PCI_10 = 10; +static const uint16_t PCI_20 = 20; +static const uint16_t PCI_30 = 30; +static const uint16_t PCI_40 = 40; +static const uint16_t PCI_NONEXISTENT = 99; +static const uint8_t SERVING_CELL_ID_SCELL1 = 1; +static const uint8_t SERVING_CELL_ID_SCELL2 = 2; +static const uint32_t UE_ID = 1; + +// Mock functions and variables +configmodule_interface_t *uniqCfg = NULL; +softmodem_params_t *get_softmodem_params(void) +{ + return NULL; +} + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) +{ + printf("detected error at %s:%d:%s: %s\n", file, line, function, s); + abort(); +} + +// Helper function to create a test cell container +static nr_rrc_cell_container_t *create_test_cell(uint64_t cell_id, uint16_t pci, sctp_assoc_t assoc_id) +{ + nr_rrc_cell_container_t *cell = calloc_or_fail(1, sizeof(*cell)); + cell->info.cell_id = cell_id; + cell->info.pci = pci; + cell->assoc_id = assoc_id; + cell->info.mode = NR_MODE_TDD; + cell->info.plmn.mcc = 222; + cell->info.plmn.mnc = 1; + cell->info.tac = 1; + return cell; +} + +// Helper function to create a test DU container +static nr_rrc_du_container_t *create_test_du(sctp_assoc_t assoc_id, uint64_t du_id, const char *name) +{ + nr_rrc_du_container_t *du = calloc_or_fail(1, sizeof(*du)); + du->assoc_id = assoc_id; + du->gNB_DU_id = du_id; + du->gNB_DU_name = strdup(name); + AssertFatal(du->gNB_DU_name != NULL, "strdup failed for DU name"); + seq_arr_init(&du->cells, sizeof(nr_rrc_cell_container_t *)); + return du; +} + +// Helper function to add DU to RRC instance +static void add_du_to_rrc(gNB_RRC_INST *rrc, nr_rrc_du_container_t *du) +{ + nr_rrc_du_container_t *collision = rrc_add_du(rrc, du); + AssertFatal(collision == NULL, "rrc_add_du should succeed for new DU (assoc_id %d)", du->assoc_id); +} + +// Helper function to find cell by PCI in global tree (for testing PCI reuse across network) +// Returns first match; PCI is not globally unique +static nr_rrc_cell_container_t *get_cell_by_pci(struct rrc_cell_tree *cells, const uint16_t pci) +{ + nr_rrc_cell_container_t *cell = NULL; + RB_FOREACH (cell, rrc_cell_tree, cells) { + if (cell->info.pci == pci) + return cell; + } + return NULL; +} + +/** @brief Test DU lookup by assoc_id + * @note Tests the functions: + * - get_du_by_assoc_id() */ +static void test_du_lookup(void) +{ + LOG_I(NR_RRC, "Testing DU lookup by assoc_id\n"); + + gNB_RRC_INST rrc = {0}; + RB_INIT(&rrc.dus); + RB_INIT(&rrc.cells); + + /* Sub-test 1: Create multiple DUs and insert into RRC DU tree */ + struct { + nr_rrc_du_container_t *du; + sctp_assoc_t assoc_id; + uint64_t du_id; + } dus[] = { + {create_test_du(ASSOC_ID_DU1, DU_ID_1, "DU-1"), ASSOC_ID_DU1, DU_ID_1}, + {create_test_du(ASSOC_ID_DU2, DU_ID_2, "DU-2"), ASSOC_ID_DU2, DU_ID_2}, + {create_test_du(ASSOC_ID_DU3, DU_ID_3, "DU-3"), ASSOC_ID_DU3, DU_ID_3}, + }; + + for (size_t i = 0; i < sizeof(dus) / sizeof(dus[0]); i++) { + add_du_to_rrc(&rrc, dus[i].du); + } + + /* Sub-test 2: Test lookup existing DUs */ + for (size_t i = 0; i < sizeof(dus) / sizeof(dus[0]); i++) { + nr_rrc_du_container_t *found = get_du_by_assoc_id(&rrc, dus[i].assoc_id); + AssertFatal(found == dus[i].du, "DU with assoc_id %d should be found", dus[i].assoc_id); + AssertFatal(found->gNB_DU_id == dus[i].du_id, "DU ID should match (expected %lu, got %lu)", dus[i].du_id, found->gNB_DU_id); + } + + /* Sub-test 3: Test lookup non-existing assoc_id */ + static const sctp_assoc_t ASSOC_ID_NONEXISTENT = 99; + nr_rrc_du_container_t *found = get_du_by_assoc_id(&rrc, ASSOC_ID_NONEXISTENT); + AssertFatal(found == NULL, "Non-existing assoc_id %d should return NULL", ASSOC_ID_NONEXISTENT); + + /* Sub-test 4: Test invalid assoc_id (0 is considered invalid) */ + static const sctp_assoc_t ASSOC_ID_INVALID = 0; + found = get_du_by_assoc_id(&rrc, ASSOC_ID_INVALID); + AssertFatal(found == NULL, "Invalid assoc_id %d should return NULL", ASSOC_ID_INVALID); + + LOG_A(NR_RRC, "DU lookup test passed\n"); + + /* Sub-test 5: Cleanup with free of DUs and their cell containers */ + for (size_t i = 0; i < sizeof(dus) / sizeof(dus[0]); i++) { + rrc_cleanup_du(&rrc, dus[i].du); + } +} + +/** @brief Test cell lookup, cell-DU association, and UE cell association + * @note Tests the functions: + * - get_cell_by_cell_id() + * - rrc_get_cell_for_du() + * - rrc_get_cell_by_pci_for_du() + * - rrc_add_cell_to_du() + * - rrc_add_ue_serving_cell() + * - rrc_get_ue_serving_cell_by_id() + * - rrc_get_pcell_for_ue() + * - rrc_remove_ue_scells_from_du() + * - rrc_free_cell_container() */ +static void test_cell_lookup(void) +{ + LOG_I(NR_RRC, "Testing cell lookup functions\n"); + + cu_init_f1_ue_data(); + + gNB_RRC_INST rrc = {0}; + RB_INIT(&rrc.dus); + RB_INIT(&rrc.cells); + + nr_rrc_du_container_t *du1 = create_test_du(ASSOC_ID_DU1, DU_ID_1, "DU-1"); + nr_rrc_du_container_t *du2 = create_test_du(ASSOC_ID_DU2, DU_ID_2, "DU-2"); + add_du_to_rrc(&rrc, du1); + add_du_to_rrc(&rrc, du2); + + /* Sub-test 1: Create multiple cells per DU and insert into global cell tree and DU's seq_arr */ + struct { + nr_rrc_cell_container_t *cell; + uint64_t cell_id; + uint16_t pci; + nr_rrc_du_container_t *du; + } cells[] = { + {create_test_cell(CELL_ID_1, PCI_10, ASSOC_ID_DU1), CELL_ID_1, PCI_10, du1}, + {create_test_cell(CELL_ID_2, PCI_20, ASSOC_ID_DU1), CELL_ID_2, PCI_20, du1}, + {create_test_cell(CELL_ID_3, PCI_1, ASSOC_ID_DU1), CELL_ID_3, PCI_1, du1}, + {create_test_cell(CELL_ID_4, PCI_2, ASSOC_ID_DU2), CELL_ID_4, PCI_2, du2}, + {create_test_cell(CELL_ID_5, PCI_3, ASSOC_ID_DU2), CELL_ID_5, PCI_3, du2}, + }; + + // Insert cells: check for duplicate cell_id first, then insert into global tree and DU's seq_arr + for (size_t i = 0; i < sizeofArray(cells); i++) { + nr_rrc_cell_container_t *existing = get_cell_by_cell_id(&rrc.cells, cells[i].cell_id); + AssertFatal(existing == NULL, "Cell ID %lu should not exist before insertion", cells[i].cell_id); + // Insert into global cell tree + nr_rrc_cell_container_t *collision = rrc_add_cell(&rrc, cells[i].cell); + AssertFatal(collision == NULL, "rrc_add_cell should succeed for new cell (ID %lu)", cells[i].cell_id); + // Insert into DU's seq_arr + nr_rrc_cell_container_t *added = rrc_add_cell_to_du(&cells[i].du->cells, cells[i].cell); + AssertFatal(added != NULL, "rrc_add_cell_to_du should succeed for new cell (ID %lu)", cells[i].cell_id); + } + + nr_rrc_cell_container_t *found; + /* Sub-test 2: Test lookup by cell_id */ + found = get_cell_by_cell_id(&rrc.cells, CELL_ID_1); + AssertFatal(found != NULL && found == cells[0].cell, "Cell ID %lu should be found", CELL_ID_1); + found = get_cell_by_cell_id(&rrc.cells, CELL_ID_4); + AssertFatal(found != NULL && found == cells[3].cell, "Cell ID %lu should be found", CELL_ID_4); + + /* Sub-test 3: Test lookup by PCI (returns first match) */ + found = get_cell_by_pci(&rrc.cells, PCI_10); + AssertFatal(found != NULL && found->info.cell_id == CELL_ID_1, + "Cell with PCI %d should be found (expected cell_id %lu)", + PCI_10, + CELL_ID_1); + + /* Sub-test 4: Test lookup non-existing cell */ + found = get_cell_by_cell_id(&rrc.cells, CELL_ID_NONEXISTENT); + AssertFatal(found == NULL, "Non-existing cell_id should return NULL"); + found = get_cell_by_pci(&rrc.cells, PCI_NONEXISTENT); + AssertFatal(found == NULL, "Non-existing PCI should return NULL"); + + /* Sub-test 5: Test rrc_get_cell_for_du() - DU-specific cell lookup */ + found = rrc_get_cell_for_du(&du1->cells, CELL_ID_1); + AssertFatal(found != NULL && found->info.cell_id == CELL_ID_1, "rrc_get_cell_for_du should find cell in DU1"); + found = rrc_get_cell_for_du(&du2->cells, CELL_ID_4); + AssertFatal(found != NULL && found->info.cell_id == CELL_ID_4, "rrc_get_cell_for_du should find cell in DU2"); + // Test non-existing cell in DU + nr_rrc_cell_container_t *not_found = rrc_get_cell_for_du(&du1->cells, CELL_ID_NONEXISTENT); + AssertFatal(not_found == NULL, "rrc_get_cell_for_du should return NULL for non-existing cell"); + // Test cell from different DU + not_found = rrc_get_cell_for_du(&du1->cells, CELL_ID_4); + AssertFatal(not_found == NULL, "rrc_get_cell_for_du should return NULL for cell from different DU"); + + /* Sub-test 6: Test rrc_get_cell_by_pci_for_du() - DU-specific PCI lookup */ + nr_rrc_cell_container_t *cell_in_du = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_10); + AssertFatal(cell_in_du != NULL, "rrc_get_cell_by_pci_for_du should find cell with PCI %d in DU1", PCI_10); + AssertFatal(cell_in_du->info.cell_id == CELL_ID_1, + "Found cell should have correct cell_id (expected %lu, got %lu)", + CELL_ID_1, + cell_in_du->info.cell_id); + cell_in_du = rrc_get_cell_by_pci_for_du(&du2->cells, PCI_2); + AssertFatal(cell_in_du != NULL, "rrc_get_cell_by_pci_for_du should find cell with PCI %d in DU2", PCI_2); + AssertFatal(cell_in_du->info.cell_id == CELL_ID_4, + "Found cell should have correct cell_id (expected %lu, got %lu)", + CELL_ID_4, + cell_in_du->info.cell_id); + // Test non-existing PCI in DU + cell_in_du = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_NONEXISTENT); + AssertFatal(cell_in_du == NULL, "rrc_get_cell_by_pci_for_du should return NULL for non-existing PCI"); + // Test PCI from different DU (should not find it in this DU) + cell_in_du = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_2); + AssertFatal(cell_in_du == NULL, "rrc_get_cell_by_pci_for_du should return NULL for PCI from different DU"); + + /* Sub-test 8: Test PCI reuse across different DUs (allowed globally) */ + // Add a cell to DU2 with the same PCI as a cell in DU1 (PCI reuse is allowed globally) + nr_rrc_cell_container_t *cell_on_du2_same_pci = create_test_cell(CELL_ID_6, PCI_10, ASSOC_ID_DU2); + nr_rrc_cell_container_t *already_in_tree = get_cell_by_cell_id(&rrc.cells, CELL_ID_6); + AssertFatal(already_in_tree == NULL, "Cell ID %lu should not exist before insertion", CELL_ID_6); + // Insert into global cell tree (PCI reuse is allowed) + nr_rrc_cell_container_t *tree_collision = rrc_add_cell(&rrc, cell_on_du2_same_pci); + AssertFatal(tree_collision == NULL, "rrc_add_cell should succeed for new cell (ID %lu)", CELL_ID_6); + // Add to DU2 (should succeed - PCI reuse across DUs is allowed) + nr_rrc_cell_container_t *added_to_du2 = rrc_add_cell_to_du(&du2->cells, cell_on_du2_same_pci); + AssertFatal(added_to_du2 != NULL, "rrc_add_cell_to_du should succeed for cell with PCI reused from different DU"); + // Verify both cells with same PCI exist in global tree (fetch by cell_id) + nr_rrc_cell_container_t *cell1 = get_cell_by_cell_id(&rrc.cells, CELL_ID_1); + nr_rrc_cell_container_t *cell6 = get_cell_by_cell_id(&rrc.cells, CELL_ID_6); + AssertFatal(cell1 != NULL && cell6 != NULL, "Both cells with same PCI should exist in global tree"); + AssertFatal(cell1->info.pci == PCI_10 && cell6->info.pci == PCI_10, "Both cells should have PCI %d", PCI_10); + // get_cell_by_pci returns first match only; verify it returns one of them + nr_rrc_cell_container_t *first_match_global = get_cell_by_pci(&rrc.cells, PCI_10); + AssertFatal(first_match_global != NULL, "get_cell_by_pci should find a cell with PCI %d", PCI_10); + AssertFatal(first_match_global == cell1 || first_match_global == cell6, + "get_cell_by_pci should return one of the two cells with PCI %d", + PCI_10); + // Verify each DU can find its own cell by PCI + nr_rrc_cell_container_t *cell_in_du1 = rrc_get_cell_by_pci_for_du(&du1->cells, PCI_10); + AssertFatal(cell_in_du1 != NULL && cell_in_du1->info.cell_id == CELL_ID_1, "DU1 should find its own cell with PCI %d", PCI_10); + nr_rrc_cell_container_t *cell_in_du2 = rrc_get_cell_by_pci_for_du(&du2->cells, PCI_10); + AssertFatal(cell_in_du2 != NULL && cell_in_du2->info.cell_id == CELL_ID_6, "DU2 should find its own cell with PCI %d", PCI_10); + + /* Sub-test 9: Test PCI reuse within same DU (should be rejected) */ + nr_rrc_cell_container_t *cell_with_duplicate_pci = create_test_cell(CELL_ID_NONEXISTENT, PCI_10, ASSOC_ID_DU1); + // Try to add to DU1 - should fail because PCI_10 already exists in DU1 + nr_rrc_cell_container_t *add_result = rrc_add_cell_to_du(&du1->cells, cell_with_duplicate_pci); + AssertFatal(add_result == NULL, "rrc_add_cell_to_du should reject cell with duplicate PCI within same DU"); + rrc_free_cell_container(cell_with_duplicate_pci); + + /* Sub-test 10: Verify cell counts per DU */ + static const int expected_du1_cells = 3; + static const int expected_du2_cells = 3; // includes cell_on_du2_same_pci + size_t count_du1 = seq_arr_size(&du1->cells); + size_t count_du2 = seq_arr_size(&du2->cells); + AssertFatal(count_du1 == expected_du1_cells, "DU 1 should have %d cells (got %zu)", expected_du1_cells, count_du1); + AssertFatal(count_du2 == expected_du2_cells, "DU 2 should have %d cells (got %zu)", expected_du2_cells, count_du2); + + /* Sub-test 11: Test UE cell association - create test UE context */ + gNB_RRC_UE_t ue = {0}; + ue.rrc_ue_id = UE_ID; + seq_arr_init(&ue.serving_cells, sizeof(ue_serving_cell_t)); + + /* Register F1 UE data for this UE at the CU */ + f1_ue_data_t f1_data = { + .secondary_ue = 0, + .e1_assoc_id = 0, + .du_assoc_id = ASSOC_ID_DU1, + }; + bool f1_added = cu_add_f1_ue_data(UE_ID, &f1_data); + AssertFatal(f1_added, "cu_add_f1_ue_data should succeed for test UE"); + + /* Sub-test 12: Test rrc_add_ue_serving_cell() - Add PCell, ue_get_pcell_entry(), rrc_get_pcell_for_ue() */ + ue_serving_cell_t *added_pcell = rrc_add_ue_serving_cell(&ue, cells[0].cell, RRC_PCELL_INDEX); + AssertFatal(added_pcell != NULL && added_pcell->nci == CELL_ID_1 && added_pcell->serving_cell_id == RRC_PCELL_INDEX, + "rrc_add_ue_serving_cell should succeed"); + const ue_serving_cell_t *pcell_entry = ue_get_pcell_entry(&ue); + AssertFatal(pcell_entry != NULL && pcell_entry->nci == CELL_ID_1 && pcell_entry->serving_cell_id == RRC_PCELL_INDEX, + "ue_get_pcell_entry should return correct PCell entry"); + nr_rrc_cell_container_t *pcell = rrc_get_pcell_for_ue(&rrc, &ue); + AssertFatal(pcell == cells[0].cell, "rrc_get_pcell_for_ue should return same cell as PCell"); + + /* Sub-test 13: Test adding SCells and rrc_get_ue_serving_cell_by_id() */ + ue_serving_cell_t *added_scell = rrc_add_ue_serving_cell(&ue, cells[1].cell, SERVING_CELL_ID_SCELL1); + AssertFatal(added_scell != NULL && added_scell->serving_cell_id == SERVING_CELL_ID_SCELL1, + "rrc_add_ue_serving_cell should succeed for SCell"); + AssertFatal(added_scell->assoc_id == ASSOC_ID_DU1, + "SCell should have correct assoc_id (expected %d, got %d)", + ASSOC_ID_DU1, + added_scell->assoc_id); + // Test rrc_get_ue_serving_cell_by_id() for PCell and SCell + ue_serving_cell_t *found_serving_cell = rrc_get_ue_serving_cell_by_id(&ue, RRC_PCELL_INDEX); + AssertFatal(found_serving_cell != NULL && found_serving_cell == added_pcell, "rrc_get_ue_serving_cell_by_id should find PCell"); + found_serving_cell = rrc_get_ue_serving_cell_by_id(&ue, SERVING_CELL_ID_SCELL1); + AssertFatal(found_serving_cell != NULL && found_serving_cell == added_scell, "rrc_get_ue_serving_cell_by_id should find SCell"); + // Test non-existing serving cell ID + ue_serving_cell_t *not_found_serving = rrc_get_ue_serving_cell_by_id(&ue, SERVING_CELL_ID_SCELL2); + AssertFatal(not_found_serving == NULL, "rrc_get_ue_serving_cell_by_id should return NULL for non-existing serving cell ID"); + static const size_t expected_serving_cells_2 = 2; + AssertFatal(seq_arr_size(&ue.serving_cells) == expected_serving_cells_2, + "UE should have %zu serving cells (got %zu)", + expected_serving_cells_2, + seq_arr_size(&ue.serving_cells)); + + // Add second SCell + nr_rrc_cell_container_t *scell2 = create_test_cell(CELL_ID_7, PCI_30, ASSOC_ID_DU1); + nr_rrc_cell_container_t *already_present = get_cell_by_cell_id(&rrc.cells, CELL_ID_7); + AssertFatal(already_present == NULL, "Cell ID %lu should not exist before insertion", CELL_ID_7); + // Insert into global cell tree + nr_rrc_cell_container_t *collision = rrc_add_cell(&rrc, scell2); + AssertFatal(collision == NULL, "rrc_add_cell should succeed for new cell (ID %lu)", CELL_ID_7); + // Insert into DU's seq_arr + nr_rrc_cell_container_t *added_scell2_du = rrc_add_cell_to_du(&du1->cells, scell2); + AssertFatal(added_scell2_du != NULL, "rrc_add_cell_to_du should succeed for new cell (ID %lu)", CELL_ID_7); + ue_serving_cell_t *added_scell2 = rrc_add_ue_serving_cell(&ue, scell2, SERVING_CELL_ID_SCELL2); + AssertFatal(added_scell2 != NULL, "rrc_add_ue_serving_cell should add new SCell"); + AssertFatal(added_scell2->serving_cell_id == SERVING_CELL_ID_SCELL2, "New SCell should have correct serving_cell_id (expected %d, got %d)", SERVING_CELL_ID_SCELL2, added_scell2->serving_cell_id); + AssertFatal(added_scell2->assoc_id == ASSOC_ID_DU1, + "SCell should have correct assoc_id (expected %d, got %d)", + ASSOC_ID_DU1, + added_scell2->assoc_id); + + /* Sub-test 14: Test multiple PCell enforcement - adding another PCell should fail if one already exists */ + nr_rrc_cell_container_t *test_cell = create_test_cell(CELL_ID_NONEXISTENT, PCI_NONEXISTENT, ASSOC_ID_DU1); + AssertFatal(rrc_add_ue_serving_cell(&ue, test_cell, RRC_PCELL_INDEX) == NULL, + "Adding another PCell when one already exists should fail"); + AssertFatal(rrc_get_pcell_for_ue(&rrc, &ue) == cells[0].cell, + "PCell should still be the original one when adding duplicate PCell fails"); + rrc_free_cell_container(test_cell); + + /* Sub-test 15: Test rrc_add_ue_serving_cell() with existing PCell (same nci, should return existing) */ + ue_serving_cell_t *existing_pcell = rrc_add_ue_serving_cell(&ue, cells[0].cell, RRC_PCELL_INDEX); + AssertFatal(existing_pcell != NULL && existing_pcell->nci == CELL_ID_1, + "rrc_add_ue_serving_cell should succeed for existing PCell with same nci"); + AssertFatal(existing_pcell == added_pcell, "Should return existing PCell entry, not create new one"); + + /* Sub-test 16: Test maximum serving cells limit */ + static const uint64_t test_cell_id_base = 10000000; + static const uint8_t starting_serving_cell_id = 3; + nr_rrc_cell_container_t *test_cells[RRC_MAX_NUM_SERVING_CELLS] = {0}; + size_t test_cell_count = 0; + for (uint8_t i = starting_serving_cell_id; i < RRC_MAX_NUM_SERVING_CELLS; i++) { + uint64_t test_cell_id = test_cell_id_base + i; + uint16_t test_pci = PCI_40 + i; + test_cells[test_cell_count] = create_test_cell(test_cell_id, test_pci, ASSOC_ID_DU1); + // Insert into global cell tree + nr_rrc_cell_container_t *existing = rrc_add_cell(&rrc, test_cells[test_cell_count]); + if (existing != NULL) { + LOG_E(NR_RRC, "Cell %lu already exists in tree, skipping insertion\n", test_cell_id); + rrc_free_cell_container(test_cells[test_cell_count]); + test_cells[test_cell_count] = NULL; + break; + } + // Insert into DU's seq_arr + rrc_add_cell_to_du(&du1->cells, test_cells[test_cell_count]); + if (rrc_add_ue_serving_cell(&ue, test_cells[test_cell_count], i) == NULL) + break; + test_cell_count++; + } + size_t final_count = seq_arr_size(&ue.serving_cells); + AssertFatal(final_count <= RRC_MAX_NUM_SERVING_CELLS, + "Should not exceed max serving cells (got %zu, max %d)", + final_count, + RRC_MAX_NUM_SERVING_CELLS); + + /* Sub-test 17: Test rrc_remove_ue_scells_from_du() */ + size_t before_remove = seq_arr_size(&ue.serving_cells); + rrc_remove_ue_scells_from_du(&ue, ASSOC_ID_DU1); + AssertFatal(seq_arr_size(&ue.serving_cells) < before_remove, "Should remove some serving cells"); + AssertFatal(ue_get_pcell_entry(&ue) == NULL, "PCell should be removed when it has matching assoc_id"); + + /* Sub-test 18: Test rrc_get_pcell_for_ue() edge case (UE with no serving cells) */ + gNB_RRC_UE_t ue_empty = {0}; + seq_arr_init(&ue_empty.serving_cells, sizeof(ue_serving_cell_t)); + AssertFatal(rrc_get_pcell_for_ue(&rrc, &ue_empty) == NULL, + "rrc_get_pcell_for_ue should return NULL for UE with no serving cells"); + seq_arr_free(&ue_empty.serving_cells, NULL); + + LOG_A(NR_RRC, "Cell lookup test passed\n"); + + /* Sub-test 19: Cleanup */ + seq_arr_free(&ue.serving_cells, NULL); + rrc_cleanup_du(&rrc, du1); + rrc_cleanup_du(&rrc, du2); +} + +int main() +{ + // Initialize logging system + logInit(); + set_log(NR_RRC, OAILOG_INFO); + + LOG_I(NR_RRC, "Starting RRC Cell Management Tests\n"); + + // DU lookup tests + test_du_lookup(); + // Cell lookup, cell-DU association, and UE cell association tests + test_cell_lookup(); + + LOG_A(NR_RRC, "All RRC Cell Management tests passed successfully!\n"); + return 0; +} diff --git a/openair3/NAS/NR_UE/nr_nas_msg.c b/openair3/NAS/NR_UE/nr_nas_msg.c index 8f4f9bd280..64f9feef54 100644 --- a/openair3/NAS/NR_UE/nr_nas_msg.c +++ b/openair3/NAS/NR_UE/nr_nas_msg.c @@ -38,6 +38,7 @@ #include #include #include "common/utils/ds/byte_array.h" +#include "common/utils/nr/nr_common.h" #include "AuthenticationResponseParameter.h" #include "FGCNasMessageContainer.h" #include "FGSDeregistrationRequestUEOriginating.h" @@ -1787,7 +1788,7 @@ static void send_nas_5gmm_ind(instance_t instance, const Guti5GSMobileIdentity_t MessageDef *msg = itti_alloc_new_message(TASK_NAS_NRUE, 0, NAS_5GMM_IND); nas_5gmm_ind_t *ind = &NAS_5GMM_IND(msg); LOG_I(NR_RRC, "5G-GUTI: AMF pointer %u, AMF Set ID %u, 5G-TMSI %u \n", guti->amfpointer, guti->amfsetid, guti->tmsi); - ind->fiveG_STMSI = ((uint64_t)guti->amfsetid << 38) | ((uint64_t)guti->amfpointer << 32) | guti->tmsi; + ind->fiveG_STMSI = nr_construct_5g_s_tmsi(guti->amfsetid, guti->amfpointer, guti->tmsi); itti_send_msg_to_task(TASK_RRC_NRUE, instance, msg); }