RRC multi-cell: add integration tests
Add integration tests for RRC cell and DU management and UE serving-cell
logic so that multi-cell behaviour and regressions can be validated
without running the full stack.
* test_du_lookup() – DU container lookup by assoc_id:
- Multiple DU creation and insertion into RRC DU tree
- Lookup of existing DUs by assoc_id
- Non-existing and invalid assoc_ids
- Cleanup of DU containers and cells
* test_cell_lookup() – Cell management and UE association:
- Cell lookup by cell_id and PCI (global and per-DU)
- DU-specific lookup: rrc_get_cell_for_du(), rrc_get_cell_by_pci_for_du()
- Duplicate cell and PCI collision handling (including PCI reuse across
DUs and rejection of duplicate PCI within same DU)
- Cell–DU association (add/remove cells from DU containers)
- UE serving cell: add PCell/SCells, get by ID, get PCell, max serving
cells limit, single PCell enforcement
- validate_ue_cell_association(), rrc_remove_ue_scells_from_du()
- Edge cases and error handling
Add openair2/RRC/NR/tests/rrc_cell_management_test.c and extend
openair2/RRC/NR/tests/CMakeLists.txt to build and register the test.
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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 <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user