From dc260bd4d974928d9c5dd119ad8263748c97fbf4 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 25 Mar 2026 10:02:47 +0100 Subject: [PATCH 01/18] Fix (config API): print correct integer pointer in range check Use the integer pointer when reporting invalid integer values: in `config_check_intrange()` error print from `param->uptr` to `param->iptr`. --- common/config/config_userapi.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/common/config/config_userapi.c b/common/config/config_userapi.c index 76f9722d0a..cb08fa8b58 100644 --- a/common/config/config_userapi.c +++ b/common/config/config_userapi.c @@ -218,8 +218,12 @@ int config_check_intrange(configmodule_interface_t *cfg, paramdef_t *param) return 0; } - fprintf(stderr,"[CONFIG] config_check_intrange: %s: %i invalid value, authorized range: %i %i\n", - param->optname, (int)*(param->uptr), param->chkPptr->s2.okintrange[0], param->chkPptr->s2.okintrange[1]); + fprintf(stderr, + "[CONFIG] config_check_intrange: %s: %i invalid value, authorized range: %i %i\n", + param->optname, + (int)*(param->iptr), + param->chkPptr->s2.okintrange[0], + param->chkPptr->s2.okintrange[1]); return -1; } From 425ea41ba52c12cc66c53d6396be8f873a5d2df9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 25 Mar 2026 10:11:58 +0100 Subject: [PATCH 02/18] fix (Config API): make config integer checks type-correct and add unsigned range validator Change 1: config_check_intval() now uses the correct integer pointer for TYPE_INT Problem: config_check_intval() previously dereferenced param->uptr regardless of param->type. For parameters declared as signed (TYPE_INT / TYPE_INT32), the active union member is param->iptr, and param->uptr may be NULL -> using uptr makes validation unsafe and type-inconsistent. What changed: Updated config_check_intval() behavior (signature unchanged). Handle both param->type, param->iptr and param->uptr. Change 1: new config_check_uintrange() for unsigned range constraints Problem: there was a signed range checker (config_check_intrange()) that reads param->iptr, but there was no dedicated unsigned range validator using param->uptr. What changed: added config_check_uintrange() in config_userapi.h/.c The new function reads param->uptr as uint32_t and validates against param->chkPptr->s2.okintrange[] endpoints. Change 3: constify input params for f2 function pointers Problem: checkedparam_t.s2.f2 was typed with non-const input params, even though range checks only read. A safer const-safe signature is required. What changed: s2.f2 now takes const configmodule_interface_t * and const paramdef_t *. config_check_intrange() and config_check_uintrange() use the same const-qualified parameters. --- common/config/config_paramdesc.h | 4 +-- common/config/config_userapi.c | 54 +++++++++++++++++++++++++++----- common/config/config_userapi.h | 3 +- 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/common/config/config_paramdesc.h b/common/config/config_paramdesc.h index 51c8626116..efb5c956d2 100644 --- a/common/config/config_paramdesc.h +++ b/common/config/config_paramdesc.h @@ -48,8 +48,8 @@ typedef union checkedparam { int num_okintval; /* number of valid values in the checkingval array */ } s1a; struct { - int (*f2)(configmodule_interface_t *cfg, - paramdef_t *param); /* check an integer against an authorized range, defined by its min and max value */ + int (*f2)(const configmodule_interface_t *cfg, + const paramdef_t *param); /* check an integer against an authorized range, defined by its min and max value */ int okintrange[CONFIG_MAX_NUMCHECKVAL]; /* integer array, store min and max values */ } s2; diff --git a/common/config/config_userapi.c b/common/config/config_userapi.c index cb08fa8b58..c009187800 100644 --- a/common/config/config_userapi.c +++ b/common/config/config_userapi.c @@ -183,14 +183,35 @@ int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param) return -1; } - for ( int i=0; ichkPptr->s1.num_okintval ; i++) { - if( *(param->uptr) == param->chkPptr->s1.okintval[i] ) { - return 0; + if (param->type == TYPE_INT32 || param->type == TYPE_INT) { + if (param->iptr == NULL) { + fprintf(stderr, "[CONFIG] config_check_intval: %s: NULL iptr\n", param->optname); + return -1; } + const int v = *param->iptr; + for (int i = 0; i < param->chkPptr->s1.num_okintval; i++) { + if (v == param->chkPptr->s1.okintval[i]) + return 0; + } + fprintf(stderr, "[CONFIG] config_check_intval: %s: %i invalid value, authorized values:\n ", param->optname, v); + for (int i = 0; i < param->chkPptr->s1.num_okintval; i++) { + fprintf(stderr, " %i", param->chkPptr->s1.okintval[i]); + } + fprintf(stderr, " \n"); + return -1; + } else { + if (param->uptr == NULL) { + fprintf(stderr, "[CONFIG] config_check_intval: %s: NULL uptr\n", param->optname); + return -1; + } + for (int i = 0; i < param->chkPptr->s1.num_okintval; i++) { + if (*(param->uptr) == (uint32_t)param->chkPptr->s1.okintval[i]) { + return 0; + } + } + print_intvalueerror(param, "config_check_intval", param->chkPptr->s1.okintval, param->chkPptr->s1.num_okintval); + return -1; } - - print_intvalueerror(param,"config_check_intval", param->chkPptr->s1.okintval,param->chkPptr->s1.num_okintval); - return -1; } int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param) @@ -211,7 +232,7 @@ int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param return -1; } -int config_check_intrange(configmodule_interface_t *cfg, paramdef_t *param) +int config_check_intrange(const configmodule_interface_t *cfg, const paramdef_t *param) { UNUSED(cfg); if( *(param->iptr) >= param->chkPptr->s2.okintrange[0] && *(param->iptr) <= param->chkPptr->s2.okintrange[1] ) { @@ -227,6 +248,25 @@ int config_check_intrange(configmodule_interface_t *cfg, paramdef_t *param) return -1; } +int config_check_uintrange(const configmodule_interface_t *cfg, const paramdef_t *param) +{ + (void)cfg; + const uint32_t *v = param->uptr; + const int *range = param->chkPptr->s2.okintrange; + + if (*v >= (uint32_t)range[0] && *v <= (uint32_t)range[1]) { + return 0; + } + + fprintf(stderr, + "[CONFIG] config_check_uintrange: %s: %u invalid, authorized range: %i %i\n", + param->optname, + *v, + range[0], + range[1]); + return -1; +} + void print_strvalueerror(paramdef_t *param, char *fname, char **okval, int numokval) { fprintf(stderr,"[CONFIG] %s: %s: %s invalid value, authorized values:\n ", fname,param->optname, *param->strptr); diff --git a/common/config/config_userapi.h b/common/config/config_userapi.h index fafbd81019..b959cb57fe 100644 --- a/common/config/config_userapi.h +++ b/common/config/config_userapi.h @@ -51,7 +51,8 @@ int config_get_processedint(configmodule_interface_t *cfg, paramdef_t *cfgoption /* functions to be used in parameters definition, to check parameters values */ int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param); int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param); -int config_check_intrange(configmodule_interface_t *cfg, paramdef_t *param); +int config_check_intrange(const configmodule_interface_t *cfg, const paramdef_t *param); +int config_check_uintrange(const configmodule_interface_t *cfg, const paramdef_t *param); int config_check_strval(configmodule_interface_t *cfg, paramdef_t *param); int config_checkstr_assign_integer(configmodule_interface_t *cfg, paramdef_t *param); const paramdef_t *config_get_paramdef_from_name(const paramdef_t *pd, int num, const char *name); From afb3005a7cbe7aa4991bd241b8a30cbe62a1095e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 16 Jan 2026 00:38:52 +0100 Subject: [PATCH 03/18] Refactor (gNB Config): reuse PLMN helper and neighbour config parsing Refactor gNB configuration code to share PLMN extraction logic and to split neighbour-cell configuration into smaller helpers with safer allocation and clearer list handling. Changes: - Add `extract_plmn_from_params()` in `gnb_config_common.c` and declare it in `gnb_config_common.h` to centralize PLMN extraction from `paramdef_t`. - Update `set_plmn_config()` to use `extract_plmn_from_params()` when filling the PLMN array instead of duplicating field assignments. - Refactor `fill_neighbour_cell_configuration()` in `gnb_config.c` to delegate per-cell neighbour parsing to `parse_neighbour_cells_list()` and sorting to `sort_neighbour_configuration()`, simplifying the main loop. - Use `extract_plmn_from_params()` when parsing neighbour PLMNs in `parse_neighbour_cells_list()` and log each neighbour with its PLMN, PCI, and other radio parameters before adding it to the sequence. - Switch allocation of `rrc->neighbour_cell_configuration` in `fill_neighbour_cell_configuration()` from `malloc` to `malloc_or_fail()` to enforce consistent handling of allocation failures. --- openair2/GNB_APP/gnb_config.c | 137 ++++++++++++--------------- openair2/GNB_APP/gnb_config_common.c | 16 +++- openair2/GNB_APP/gnb_config_common.h | 2 + 3 files changed, 75 insertions(+), 80 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index a73a77b0ee..10ab9a73d0 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2012,94 +2012,81 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int return NULL; } -static void fill_neighbour_cell_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc) +static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, const paramlist_def_t *list, const char *n_path) { - char gnbpath[MAX_OPTNAME_SIZE + 8]; - snprintf(gnbpath, sizeof(gnbpath), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx); + cell->neighbour_cells = malloc_or_fail(sizeof(seq_arr_t)); + seq_arr_init(cell->neighbour_cells, sizeof(nr_neighbour_cell_t)); - GET_PARAMS_LIST(neighbour_list_param_list, - neighbour_list_params, - GNB_NEIGHBOUR_LIST_PARAM_LIST, - GNB_CONFIG_STRING_NEIGHBOUR_LIST, - gnbpath); + for (int l = 0; l < list->numelt; ++l) { + const paramdef_t *cell_params = list->paramarray[l]; + nr_neighbour_cell_t n = {.gNB_ID = *cell_params[GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr, + .nrcell_id = (uint64_t)*cell_params[GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr, + .physicalCellId = *cell_params[GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr, + .subcarrierSpacing = *cell_params[GNB_CONFIG_N_CELL_SCS_IDX].uptr, + .band = *cell_params[GNB_CONFIG_N_CELL_BAND_IDX].uptr, + .absoluteFrequencySSB = *cell_params[GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr, + .tac = *cell_params[GNB_CONFIG_N_CELL_TAC_IDX].uptr}; - if (neighbour_list_param_list.numelt < 1) - return; + char p[CONFIG_MAXOPTLENGTH]; // plmn path + snprintf(p, sizeof(p), "%s.%s.[%i].%s", n_path, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, l, GNB_CONFIG_STRING_NEIGHBOUR_PLMN); + GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, p); + n.plmn = extract_plmn_from_params(NeighbourPlmn, sizeofArray(NeighbourPlmn)); - rrc->neighbour_cell_configuration = malloc(sizeof(seq_arr_t)); - seq_arr_init(rrc->neighbour_cell_configuration, sizeof(neighbour_cell_configuration_t)); + LOG_I(GNB_APP, + "Neighbour[%d]: cellId %ld, PLMN %03u.%0*u, gNB %d, PCI %d, SCS %d, SSB ARFCN %u, TAC %u\n", + l, + n.nrcell_id, + n.plmn.mcc, + n.plmn.mnc_digit_length, + n.plmn.mnc, + n.gNB_ID, + n.physicalCellId, + n.subcarrierSpacing, + n.absoluteFrequencySSB, + n.tac); - for (int elm = 0; elm < neighbour_list_param_list.numelt; ++elm) { - neighbour_cell_configuration_t cell = {0}; - cell.nr_cell_id = (uint64_t)*neighbour_list_param_list.paramarray[elm][0].u64ptr; - - char neighbourpath[MAX_OPTNAME_SIZE + 8]; - snprintf(neighbourpath, sizeof(neighbourpath), "%s.[%i].%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_NEIGHBOUR_LIST, elm); - GET_PARAMS_LIST(NeighbourCellParamList, - NeighbourCellParams, - GNBNEIGHBOURCELLPARAMS_DESC, - GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, - neighbourpath); - LOG_I(GNB_APP, "Cell %ld has %d neighbours\n", cell.nr_cell_id, NeighbourCellParamList.numelt); - if (NeighbourCellParamList.numelt < 1) - continue; - - cell.neighbour_cells = malloc_or_fail(sizeof(seq_arr_t)); - seq_arr_init(cell.neighbour_cells, sizeof(nr_neighbour_cell_t)); - for (int l = 0; l < NeighbourCellParamList.numelt; ++l) { - nr_neighbour_cell_t n = {0}; - n.gNB_ID = *(NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr); - n.nrcell_id = (uint64_t) * (NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr); - n.physicalCellId = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr; - n.subcarrierSpacing = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_SCS_IDX].uptr; - n.band = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_BAND_IDX].uptr; - n.absoluteFrequencySSB = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr; - n.tac = *NeighbourCellParamList.paramarray[l][GNB_CONFIG_N_CELL_TAC_IDX].uptr; - - char neighbour_plmn_path[CONFIG_MAXOPTLENGTH]; - snprintf(neighbour_plmn_path, - sizeof(neighbour_plmn_path), - "%s.%s.[%i].%s", - neighbourpath, - GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, - l, - GNB_CONFIG_STRING_NEIGHBOUR_PLMN); - GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, neighbour_plmn_path); - - 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", - l, - n.gNB_ID, - n.nrcell_id, - n.physicalCellId, - n.subcarrierSpacing, - n.absoluteFrequencySSB, - n.tac, - n.plmn.mcc, - n.plmn.mnc_digit_length, - n.plmn.mnc); + if (get_neighbour_by_pci(cell->neighbour_cells, n.physicalCellId) != NULL) { + LOG_E(GNB_APP, "Cell %ld: duplicate PCI %d in neighbour list (NCI=%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(rrc->neighbour_cell_configuration, &cell, sizeof(cell)); + + seq_arr_push_back(cell->neighbour_cells, &n, sizeof(n)); } +} + +static void sort_neighbour_configuration(gNB_RRC_INST *rrc) +{ void *base = seq_arr_front(rrc->neighbour_cell_configuration); size_t nmemb = seq_arr_size(rrc->neighbour_cell_configuration); size_t element_size = sizeof(neighbour_cell_configuration_t); - qsort(base, nmemb, element_size, sort_neighbour_cell_config_by_cell_id); } +static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INST *rrc) +{ + char path[MAX_OPTNAME_SIZE + 8]; + snprintf(path, sizeof(path), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx); + GET_PARAMS_LIST(nlist, nparams, GNB_NEIGHBOUR_LIST_PARAM_LIST, GNB_CONFIG_STRING_NEIGHBOUR_LIST, path); + + if (nlist.numelt < 1) + return; + + rrc->neighbour_cell_configuration = malloc_or_fail(sizeof(seq_arr_t)); + seq_arr_init(rrc->neighbour_cell_configuration, sizeof(neighbour_cell_configuration_t)); + + for (int elm = 0; elm < nlist.numelt; ++elm) { + neighbour_cell_configuration_t cell = {.nr_cell_id = *nlist.paramarray[elm][0].u64ptr}; + snprintf(path, sizeof(path), "%s.[%i].%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_NEIGHBOUR_LIST, elm); + GET_PARAMS_LIST(ncell_list, ncell_params, GNBNEIGHBOURCELLPARAMS_DESC, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, path); + if (ncell_list.numelt < 1) + continue; + parse_neighbour_cells_list(&cell, &ncell_list, path); + seq_arr_push_back(rrc->neighbour_cell_configuration, &cell, sizeof(cell)); + } + + sort_neighbour_configuration(rrc); +} + static void fill_measurement_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc) { char measurement_path[MAX_OPTNAME_SIZE + 8]; diff --git a/openair2/GNB_APP/gnb_config_common.c b/openair2/GNB_APP/gnb_config_common.c index 395af98750..5aed92f810 100644 --- a/openair2/GNB_APP/gnb_config_common.c +++ b/openair2/GNB_APP/gnb_config_common.c @@ -31,19 +31,25 @@ uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uin return num_ssi; } +plmn_id_t extract_plmn_from_params(const paramdef_t *params, int n_params) +{ + plmn_id_t plmn = {.mcc = *gpd(params, n_params, GNB_CONFIG_STRING_MOBILE_COUNTRY_CODE)->uptr, + .mnc = *gpd(params, n_params, GNB_CONFIG_STRING_MOBILE_NETWORK_CODE)->uptr, + .mnc_digit_length = *gpd(params, n_params, GNB_CONFIG_STRING_MNC_DIGIT_LENGTH)->uptr}; + AssertFatal((plmn.mnc_digit_length == 2) || (plmn.mnc_digit_length == 3), "BAD MNC DIGIT LENGTH %d", plmn.mnc_digit_length); + return plmn; +} + uint8_t set_plmn_config(plmn_id_t *p, uint8_t idx) { char gnbpath[MAX_OPTNAME_SIZE * 2 + 8]; snprintf(gnbpath, sizeof(gnbpath), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, idx); GET_PARAMS_LIST(PLMNParamList, PLMNParams, GNBPLMNPARAMS_DESC, GNB_CONFIG_STRING_PLMN_LIST, gnbpath, PLMNPARAMS_CHECK); uint8_t num_plmn = PLMNParamList.numelt; + const int n_plmn_params = sizeofArray(PLMNParams); AssertFatal(num_plmn >= 1 && num_plmn <= 6, "The number of PLMN IDs must be in [1,6], but is %d\n", num_plmn); for (int l = 0; l < num_plmn; ++l) { - plmn_id_t *plmn = &p[l]; - plmn->mcc = *PLMNParamList.paramarray[l][GNB_MOBILE_COUNTRY_CODE_IDX].uptr; - plmn->mnc = *PLMNParamList.paramarray[l][GNB_MOBILE_NETWORK_CODE_IDX].uptr; - plmn->mnc_digit_length = *PLMNParamList.paramarray[l][GNB_MNC_DIGIT_LENGTH].u8ptr; - AssertFatal((plmn->mnc_digit_length == 2) || (plmn->mnc_digit_length == 3), "BAD MNC DIGIT LENGTH %d", plmn->mnc_digit_length); + p[l] = extract_plmn_from_params(PLMNParamList.paramarray[l], n_plmn_params); } return num_plmn; } diff --git a/openair2/GNB_APP/gnb_config_common.h b/openair2/GNB_APP/gnb_config_common.h index ae2ae4e799..bfa875c13d 100644 --- a/openair2/GNB_APP/gnb_config_common.h +++ b/openair2/GNB_APP/gnb_config_common.h @@ -4,6 +4,8 @@ #include #include "common/5g_platform_types.h" +#include "common/config/config_paramdesc.h" uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uint8_t l); uint8_t set_plmn_config(plmn_id_t *p, uint8_t idx); +plmn_id_t extract_plmn_from_params(const paramdef_t *params, int n_params); From 196855364caa657b62f43efb2b3f2c03f052a503 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 11:48:58 +0100 Subject: [PATCH 04/18] Add documentation to refactored functions in gnb_config.c --- openair2/GNB_APP/gnb_config.c | 11 +++++++++++ openair2/GNB_APP/gnb_config_common.c | 3 +++ 2 files changed, 14 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 10ab9a73d0..c758b391b8 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2012,11 +2012,16 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int return NULL; } +/** @brief Parse and add neighbour cells for a given cell configuration + * @param[in,out] cell Neighbour cell configuration to populate + * @param[in] list Parameter list describing neighbour cells + * @param[in] n_path Base path for neighbour configuration in the config tree */ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, const paramlist_def_t *list, const char *n_path) { cell->neighbour_cells = malloc_or_fail(sizeof(seq_arr_t)); seq_arr_init(cell->neighbour_cells, sizeof(nr_neighbour_cell_t)); + /* Parse each neighbour cell in the list */ for (int l = 0; l < list->numelt; ++l) { const paramdef_t *cell_params = list->paramarray[l]; nr_neighbour_cell_t n = {.gNB_ID = *cell_params[GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr, @@ -2045,6 +2050,7 @@ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, con n.absoluteFrequencySSB, n.tac); + /* Check for duplicate PCI in the neighbour list */ if (get_neighbour_by_pci(cell->neighbour_cells, n.physicalCellId) != NULL) { LOG_E(GNB_APP, "Cell %ld: duplicate PCI %d in neighbour list (NCI=%ld)\n", cell->nr_cell_id, n.physicalCellId, n.nrcell_id); AssertFatal(false, "PCI must be unique within a cell's neighbour list\n"); @@ -2054,6 +2060,8 @@ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, con } } +/** @brief Sort neighbour cell configuration by cell ID + * @param[in] rrc Pointer to RRC instance */ static void sort_neighbour_configuration(gNB_RRC_INST *rrc) { void *base = seq_arr_front(rrc->neighbour_cell_configuration); @@ -2062,6 +2070,9 @@ static void sort_neighbour_configuration(gNB_RRC_INST *rrc) qsort(base, nmemb, element_size, sort_neighbour_cell_config_by_cell_id); } +/** @brief Fill neighbour cell configuration from config file + * @param[in] gnb_idx gNB index + * @param[in,out] rrc Pointer to RRC instance */ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INST *rrc) { char path[MAX_OPTNAME_SIZE + 8]; diff --git a/openair2/GNB_APP/gnb_config_common.c b/openair2/GNB_APP/gnb_config_common.c index 5aed92f810..aca2457d08 100644 --- a/openair2/GNB_APP/gnb_config_common.c +++ b/openair2/GNB_APP/gnb_config_common.c @@ -31,6 +31,9 @@ uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uin return num_ssi; } +/** @brief Extract PLMN from parameter array + * @param[in] params Parameter array containing PLMN fields + * @return plmn_id_t structure filled from parameters */ plmn_id_t extract_plmn_from_params(const paramdef_t *params, int n_params) { plmn_id_t plmn = {.mcc = *gpd(params, n_params, GNB_CONFIG_STRING_MOBILE_COUNTRY_CODE)->uptr, From 442b3045d6c0f8a8543fef86ba2e189321f36d08 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 3 Mar 2026 14:54:59 +0100 Subject: [PATCH 05/18] Refactor (gNB Config): look up neighbour-list nr_cellid by name with gpd Outer gNB neighbour_list entries now only declare the serving cell nr_cellid. The code resolves that field with gpd instead of a fixed column index. Changes: - gnb_paramdef.h: remove unused NEIGHBOUR_CELL_PHYSICAL_ID from GNB_NEIGHBOUR_LIST_PARAM_LIST (keep NRCELLID only). - gnb_config.c: in fill_neighbour_cell_configuration, set nr_cell_id from gpd --- openair2/GNB_APP/gnb_config.c | 3 ++- openair2/GNB_APP/gnb_paramdef.h | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index c758b391b8..49ff67a8de 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2086,7 +2086,8 @@ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INS seq_arr_init(rrc->neighbour_cell_configuration, sizeof(neighbour_cell_configuration_t)); for (int elm = 0; elm < nlist.numelt; ++elm) { - neighbour_cell_configuration_t cell = {.nr_cell_id = *nlist.paramarray[elm][0].u64ptr}; + neighbour_cell_configuration_t cell = {0}; + cell.nr_cell_id = *gpd(nlist.paramarray[elm], sizeofArray(nparams), GNB_CONFIG_STRING_NRCELLID)->u64ptr; snprintf(path, sizeof(path), "%s.[%i].%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_NEIGHBOUR_LIST, elm); GET_PARAMS_LIST(ncell_list, ncell_params, GNBNEIGHBOURCELLPARAMS_DESC, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, path); if (ncell_list.numelt < 1) diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 8265303969..05714771b4 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -289,7 +289,6 @@ typedef enum { #define GNB_NEIGHBOUR_LIST_PARAM_LIST { \ /* optname helpstr paramflags XXXptr def val type numelt */ \ {GNB_CONFIG_STRING_NRCELLID, "cell nrCell Id which has neighbours", PARAMFLAG_MANDATORY, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0}, \ - {GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID, "neighbour cell physical id", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ } // clang-format on #define GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST "neighbour_cell_configuration" From 185690cb94cac5071cbcbf383dc4aa97d7525a24 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 17:28:47 +0100 Subject: [PATCH 06/18] Refactor (RRC): inline neighbour cell arrays and constify access Inline neighbour cell arrays into the RRC neighbour configuration and update RRC/DU helpers to use const-aware access and shared iteration helpers when walking neighbour lists and building measurement configs. Changes: - Change neighbour_cells in neighbour_cell_configuration_t from a seq_arr_t * to an embedded seq_arr_t field. - Initialize and populate the embedded neighbour_cells directly in parse_neighbour_cells_list in gnb_config.c, dropping dynamic allocation of the neighbour list container. - Refactor get_neighbour_cell_by_pci and nr_rrc_get_measconfig in rrc_gNB.c to access the embedded neighbour_cells via const pointers, introduce a local meas_cfg and a3_event_list, and iterate neighbour cells with FOR_EACH_SEQ_ARR. - Treat neighbour cell configuration entries as const in rrc_gNB_du.c (get_cell_neighbour_list, label_intra_frequency_neighbours, valid_du_in_neighbour_configs) and iterate neighbour lists with FOR_EACH_SEQ_ARR instead of index-based loops. --- openair2/GNB_APP/gnb_config.c | 7 +++---- openair2/RRC/NR/nr_rrc_defs.h | 2 +- openair2/RRC/NR/rrc_gNB.c | 27 +++++++++++++-------------- openair2/RRC/NR/rrc_gNB_du.c | 16 +++++++--------- 4 files changed, 24 insertions(+), 28 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 49ff67a8de..213584a44d 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2018,8 +2018,7 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int * @param[in] n_path Base path for neighbour configuration in the config tree */ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, const paramlist_def_t *list, const char *n_path) { - cell->neighbour_cells = malloc_or_fail(sizeof(seq_arr_t)); - seq_arr_init(cell->neighbour_cells, sizeof(nr_neighbour_cell_t)); + seq_arr_init(&cell->neighbour_cells, sizeof(nr_neighbour_cell_t)); /* Parse each neighbour cell in the list */ for (int l = 0; l < list->numelt; ++l) { @@ -2051,12 +2050,12 @@ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, con n.tac); /* Check for duplicate PCI in the neighbour list */ - if (get_neighbour_by_pci(cell->neighbour_cells, n.physicalCellId) != NULL) { + if (get_neighbour_by_pci(&cell->neighbour_cells, n.physicalCellId) != NULL) { LOG_E(GNB_APP, "Cell %ld: duplicate PCI %d in neighbour list (NCI=%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)); + seq_arr_push_back(&cell->neighbour_cells, &n, sizeof(n)); } } diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 606eb86327..3b07ec6a6c 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -326,7 +326,7 @@ typedef struct { typedef struct neighbour_cell_configuration_s { uint64_t nr_cell_id; - seq_arr_t *neighbour_cells; + seq_arr_t neighbour_cells; } neighbour_cell_configuration_t; typedef struct nr_mac_rrc_dl_if_s { diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 096dc98eea..4c47e6a23b 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -263,10 +263,8 @@ static bool eq_pci(const void *vval, const void *vit) const nr_neighbour_cell_t *get_neighbour_cell_by_pci(const neighbour_cell_configuration_t *cell, int pci) { - seq_arr_t *head = cell->neighbour_cells; - DevAssert(head != NULL); - LOG_D(NR_RRC, "Number of neighbour cells: %ld\n", head->size); - elm_arr_t e = find_if(head, &pci, eq_pci); + DevAssert(cell); + elm_arr_t e = find_if((seq_arr_t *)&cell->neighbour_cells, &pci, eq_pci); if (e.found) { const nr_neighbour_cell_t *neighbour = (const nr_neighbour_cell_t *)e.it; LOG_D(NR_RRC, "Found matching neighbour cell with PCI %d and Cell ID %ld\n", neighbour->physicalCellId, neighbour->nrcell_id); @@ -772,23 +770,23 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell 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.cell_id); - seq_arr_t *neighbour_cells = NULL; - if (neighbour_config) - neighbour_cells = neighbour_config->neighbour_cells; + const seq_arr_t *neighbour_cells = neighbour_config ? &neighbour_config->neighbour_cells : NULL; int *neigh_a3_id = NULL; if (neighbour_cells && neighbour_cells->size) neigh_a3_id = calloc_or_fail(neighbour_cells->size, sizeof(int)); - if (neighbour_cells && rrc->measurementConfiguration.a3_event_list && rrc->measurementConfiguration.a3_event_list->size > 0) { + const nr_measurement_configuration_t *meas_cfg = &rrc->measurementConfiguration; + const seq_arr_t *a3_event_list = meas_cfg->a3_event_list; + if (neighbour_cells && a3_event_list && a3_event_list->size > 0) { /* Loop through neighbours and find related A3 configuration If no related A3 but there is default add the default one. If default one added once as a report, no need to add it again && duplication. */ LOG_D(NR_RRC, "Preparing A3 Event Measurement Configuration!\n"); bool default_a3_added = false; // To ensure that the default configuration is only added once - for (int i = 0; i < neighbour_cells->size; i++) { - nr_neighbour_cell_t *neighbourCell = (nr_neighbour_cell_t *)seq_arr_at(neighbour_cells, i); + int i = 0; + FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, neighbourCell, neighbour_cells) { seq_arr_push_back(&neigh_seq, neighbourCell, sizeof(nr_neighbour_cell_t)); const nr_a3_event_t *a3Event = get_a3_configuration((gNB_RRC_INST *)rrc, neighbourCell->physicalCellId); if (!a3Event) { @@ -814,12 +812,13 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell } NR_ReportConfigId_t reportConfigId = neigh_a3_id[i]; seq_arr_push_back(&rc_A3_seq, prepare_a3_event_report(a3Event, reportConfigId), sizeof(NR_ReportConfigToAddMod_t)); + i++; } } - if (rrc->measurementConfiguration.per_event) - rc_PER = prepare_periodic_event_report(rrc->measurementConfiguration.per_event); - if (rrc->measurementConfiguration.a2_event) - rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.a2_event); + if (meas_cfg->per_event) + rc_PER = prepare_periodic_event_report(meas_cfg->per_event); + if (meas_cfg->a2_event) + rc_A2 = prepare_a2_event_report(meas_cfg->a2_event); NR_MeasConfig_t *result = get_MeasConfig(mt, band, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index ec139e684d..71da3f3d1b 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -152,7 +152,7 @@ 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 nr_rrc_cell_container_t *cell) +static const 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); @@ -160,7 +160,7 @@ static neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_RRC_INS void *it = bsearch((void *)&cell->info.cell_id, base, nmemb, size, nr_cell_id_match); - return (neighbour_cell_configuration_t *)it; + return (const neighbour_cell_configuration_t *)it; } static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) @@ -183,7 +183,7 @@ static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_cel if (!rrc->neighbour_cell_configuration) return; - neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell); + const neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell); if (!neighbour_cell_config) return; @@ -194,8 +194,8 @@ static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_cel 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); + const seq_arr_t *cell_neighbour_list = &neighbour_cell_config->neighbour_cells; + for_each((seq_arr_t *)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) @@ -206,10 +206,8 @@ static bool valid_du_in_neighbour_configs(const seq_arr_t *neighbour_cell_config 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++) { - const nr_neighbour_cell_t *nc = seq_arr_at(neighbour_config->neighbour_cells, ni); + FOR_EACH_SEQ_ARR(const neighbour_cell_configuration_t *, neighbour_config, neighbour_cell_configuration) { + FOR_EACH_SEQ_ARR(const nr_neighbour_cell_t *, nc, &neighbour_config->neighbour_cells) { if (nc->nrcell_id != cell->nr_cellid) continue; // current cell is in the nc config, check that config matches From 7606a93f57ec77b0472e98ecd95df364e6c41271 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 7 Apr 2026 11:20:25 +0200 Subject: [PATCH 07/18] Refactor (RRC): validate neighbour-cell params and use gpd lookups Tighten neighbour-cell config parsing by enforcing parameter constraints in the descriptor and switching parsing code to name-based lookups with gpd. Changes: - Update `GNBNEIGHBOURCELLPARAMS_DESC` in `openair2/GNB_APP/gnb_paramdef.h` to add checks for `physical id`, `absoluteFrequencySSB`, `scs`, `band` `tracking_area_code`. - Remove neighbour-cell parameter index macros. - Refactor `parse_neighbour_cells_list` in `openair2/GNB_APP/gnb_config.c` to use `gpd()` lookups for neighbour fields instead of fixed indexes. - Extend `parse_neighbour_cells_list` with an `n_cell_params` argument and update `fill_neighbour_cell_configuration` to pass `sizeofArray(ncell_params)`. --- openair2/GNB_APP/gnb_config.c | 25 +++++++++++++--------- openair2/GNB_APP/gnb_paramdef.h | 38 +++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 213584a44d..0c38993487 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2015,21 +2015,26 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int /** @brief Parse and add neighbour cells for a given cell configuration * @param[in,out] cell Neighbour cell configuration to populate * @param[in] list Parameter list describing neighbour cells - * @param[in] n_path Base path for neighbour configuration in the config tree */ -static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, const paramlist_def_t *list, const char *n_path) + * @param[in] n_path Base path for neighbour configuration in the config tree + * @param[in] n_cell_params Number of neighbour-cell parameters */ +static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, + const paramlist_def_t *list, + const char *n_path, + int n_cell_params) { seq_arr_init(&cell->neighbour_cells, sizeof(nr_neighbour_cell_t)); /* Parse each neighbour cell in the list */ for (int l = 0; l < list->numelt; ++l) { const paramdef_t *cell_params = list->paramarray[l]; - nr_neighbour_cell_t n = {.gNB_ID = *cell_params[GNB_CONFIG_N_CELL_GNB_ID_IDX].uptr, - .nrcell_id = (uint64_t)*cell_params[GNB_CONFIG_N_CELL_NR_CELLID_IDX].u64ptr, - .physicalCellId = *cell_params[GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX].uptr, - .subcarrierSpacing = *cell_params[GNB_CONFIG_N_CELL_SCS_IDX].uptr, - .band = *cell_params[GNB_CONFIG_N_CELL_BAND_IDX].uptr, - .absoluteFrequencySSB = *cell_params[GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX].i64ptr, - .tac = *cell_params[GNB_CONFIG_N_CELL_TAC_IDX].uptr}; + nr_neighbour_cell_t n = { + .gNB_ID = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_GNB_ID)->uptr, + .nrcell_id = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NRCELLID)->u64ptr, + .physicalCellId = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID)->uptr, + .subcarrierSpacing = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_SCS)->uptr, + .band = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND)->uptr, + .absoluteFrequencySSB = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB)->i64ptr, + .tac = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE)->uptr}; char p[CONFIG_MAXOPTLENGTH]; // plmn path snprintf(p, sizeof(p), "%s.%s.[%i].%s", n_path, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, l, GNB_CONFIG_STRING_NEIGHBOUR_PLMN); @@ -2091,7 +2096,7 @@ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INS GET_PARAMS_LIST(ncell_list, ncell_params, GNBNEIGHBOURCELLPARAMS_DESC, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, path); if (ncell_list.numelt < 1) continue; - parse_neighbour_cells_list(&cell, &ncell_list, path); + parse_neighbour_cells_list(&cell, &ncell_list, path, sizeofArray(ncell_params)); seq_arr_push_back(rrc->neighbour_cell_configuration, &cell, sizeof(cell)); } diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 05714771b4..50184503ea 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -300,23 +300,29 @@ typedef enum { #define GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE "tracking_area_code" #define GNB_CONFIG_STRING_NEIGHBOUR_PLMN "plmn" -#define GNB_CONFIG_N_CELL_GNB_ID_IDX 0 -#define GNB_CONFIG_N_CELL_NR_CELLID_IDX 1 -#define GNB_CONFIG_N_CELL_PHYSICAL_ID_IDX 2 -#define GNB_CONFIG_N_CELL_ABS_FREQ_SSB_IDX 3 -#define GNB_CONFIG_N_CELL_SCS_IDX 4 -#define GNB_CONFIG_N_CELL_BAND_IDX 5 -#define GNB_CONFIG_N_CELL_TAC_IDX 6 // clang-format off -#define GNBNEIGHBOURCELLPARAMS_DESC { \ -/* optname helpstr paramflags XXXptr def val type numelt */ \ - {GNB_CONFIG_STRING_GNB_ID, "neighbour cell's gNB ID", PARAMFLAG_MANDATORY, .uptr=NULL, .defintval=0, TYPE_UINT, 0}, \ - {GNB_CONFIG_STRING_NRCELLID, "neighbour cell nrCell Id", PARAMFLAG_MANDATORY, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0}, \ - {GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID, "neighbour cell physical id", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ - {GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB, "neighbour cell abs freq ssb", PARAMFLAG_MANDATORY, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0}, \ - {GNB_CONFIG_STRING_NEIGHBOUR_CELL_SCS, "neighbour cell scs", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ - {GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND, "neighbour cell band", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=78, TYPE_UINT, 0}, \ - {GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE, "neighbour cell tracking area", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ +#define GNBNEIGHBOURCELLPARAMS_DESC { /* optname helpstr paramflags XXXptr def val type numelt */ \ + {GNB_CONFIG_STRING_GNB_ID, \ + "neighbour cell's gNB ID", PARAMFLAG_MANDATORY, .uptr=NULL, .defintval=0, TYPE_UINT, 0, \ + .chkPptr=NULL}, \ + {GNB_CONFIG_STRING_NRCELLID, \ + "neighbour cell nrCell Id", PARAMFLAG_MANDATORY, .u64ptr=NULL, .defint64val=0, TYPE_UINT64, 0, \ + .chkPptr=NULL}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_PHYSICAL_ID, \ + "neighbour cell physical id", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 1007}}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB, \ + "neighbour cell absolute frequency SSB (0..3279165)", PARAMFLAG_MANDATORY, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 3279165}}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_SCS, \ + "neighbour cell scs (15/30/60/120 kHz)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {0, 1, 2, 3}, 4}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND, \ + "neighbour cell band (1..1024)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=78, TYPE_UINT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {1, 1024}}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE, \ + "neighbour cell tracking area (0..16777215)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 16777215}}}}, \ } // clang-format on From e27f8c24cf229769807b63b1081e113aa7b21d0c Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 5 Mar 2026 11:23:39 +0100 Subject: [PATCH 08/18] Refactor (F1AP/RRC): convert SIB containers to byte_array_t MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unify SIB container handling by introducing a typed SIB enum and storing SIB payloads as byte_array_t across common NR types, F1AP helpers, MAC, and RRC DU setup code. Changes: - Include byte_array support in nr_common.h, add nr_sib_type_t (NR_SIB_1–NR_SIB_21), and change nr_SIBs_t to hold only nr_sib_type_t SIB_type. - Switch f1ap_sib_msg_t in f1ap_messages_types.h to use a byte_array_t SI_container instead of raw pointer and length fields. - Update F1AP encode/decode, equality, copy, and free helpers in f1ap_interface_management.c to work on SI_container.buf / SI_container.len and use eq_byte_array, copy_byte_array, and free_byte_array. - Adapt F1AP tests in f1ap_lib_test.c to build and inspect SIB containers via SI_container.buf and SI_container.len. - Replace magic SIB numbers with nr_sib_type_t values in gnb_config.c (get_sys_info and fill_du_sibs) to validate and configure DU SIBs. - Rework nr_mac_configure_other_sib in NR_MAC_gNB/config.c to decode SIB2 from a byte_array_t container, fix freeing by releasing the decoded sib2 on failure, and use NR_SIB_2 / NR_SIB_19 for CU/DU SIB selection. - Add an add_si_msg helper in rrc_gNB_du.c and refactor the SIB2 branch of rrc_gNB_process_f1_setup_req to populate cell->SI_msg from encoded local byte array while iterating SIBs with FOR_EACH_SEQ_ARR. --- common/utils/nr/nr_common.h | 29 ++++++++++++-- openair2/COMMON/f1ap_messages_types.h | 3 +- openair2/F1AP/lib/f1ap_interface_management.c | 39 +++++++------------ openair2/F1AP/tests/f1ap_lib_test.c | 18 ++++----- openair2/GNB_APP/gnb_config.c | 4 +- openair2/LAYER2/NR_MAC_gNB/config.c | 19 +++++---- openair2/RRC/NR/rrc_gNB_du.c | 24 ++++++++---- 7 files changed, 78 insertions(+), 58 deletions(-) diff --git a/common/utils/nr/nr_common.h b/common/utils/nr/nr_common.h index 8580a0491b..228e51feb2 100644 --- a/common/utils/nr/nr_common.h +++ b/common/utils/nr/nr_common.h @@ -13,6 +13,7 @@ #include #include "assertions.h" #include "common/utils/utils.h" +#include "common/utils/ds/byte_array.h" #define MAX_SI_GROUPS 3 #define NR_MAX_PDSCH_TBS 3824 @@ -79,10 +80,32 @@ static inline const char *rnti_types(nr_rnti_type_t rr) // the total shift is 2 * 15, in dB scale thats 10log10(2^(15*2)) #define SQ15_SQUARED_NORM_FACTOR_DB 90.3089986992 +typedef enum { + NR_SIB_1 = 1, + NR_SIB_2, + NR_SIB_3, + NR_SIB_4, + NR_SIB_5, + NR_SIB_6, + NR_SIB_7, + NR_SIB_8, + NR_SIB_9, + NR_SIB_10, + NR_SIB_11, + NR_SIB_12, + NR_SIB_13, + NR_SIB_14, + NR_SIB_15, + NR_SIB_16, + NR_SIB_17, + NR_SIB_18, + NR_SIB_19, + NR_SIB_20, + NR_SIB_21, +} nr_sib_type_t; + typedef struct { - uint8_t *SIB_buffer; - int SIB_size; - int SIB_type; + nr_sib_type_t SIB_type; } nr_SIBs_t; typedef struct nr_bandentry_s { diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 37c4a7ee83..2df2c783bf 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -161,8 +161,7 @@ typedef struct f1ap_du_register_req_s { typedef struct f1ap_sib_msg_s { /// RRC container with system information owned by gNB-CU - uint8_t *SI_container; - int SI_container_length; + byte_array_t SI_container; /// SIB block type, e.g. 2 for sibType2 int SI_type; } f1ap_sib_msg_t; diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index bb2015b33e..815a9f1b82 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -627,10 +627,10 @@ static void encode_cells_to_activate(const served_cells_to_activate_t *cell, F1A F1AP_GNB_CUSystemInformation_t *gNB_CUSystemInformation = &cells_to_be_activated_itemExtIEs->extensionValue.choice.GNB_CUSystemInformation; const f1ap_sib_msg_t *SI_msg = &cell->SI_msg[n]; - if (SI_msg->SI_container != NULL) { + if (SI_msg->SI_container.buf != NULL) { asn1cSequenceAdd(gNB_CUSystemInformation->sibtypetobeupdatedlist.list, F1AP_SibtypetobeupdatedListItem_t, sib_item); sib_item->sIBtype = SI_msg->SI_type; - OCTET_STRING_fromBuf(&sib_item->sIBmessage, (const char *)SI_msg->SI_container, SI_msg->SI_container_length); + OCTET_STRING_fromBuf(&sib_item->sIBmessage, (const char *)SI_msg->SI_container.buf, SI_msg->SI_container.len); } } } @@ -670,7 +670,7 @@ static bool decode_cells_to_activate(served_cells_to_activate_t *out, const F1AP F1AP_SibtypetobeupdatedListItem_t *sib_item = gNB_CUSystemInformation->sibtypetobeupdatedlist.list.array[s]; /* SI container */ f1ap_sib_msg_t *SI_msg = &out->SI_msg[s]; - SI_msg->SI_container = cp_octet_string(&sib_item->sIBmessage, &SI_msg->SI_container_length); + SI_msg->SI_container.buf = cp_octet_string(&sib_item->sIBmessage, (int *)&SI_msg->SI_container.len); /* SIB type */ SI_msg->SI_type = sib_item->sIBtype; } @@ -1163,8 +1163,8 @@ bool eq_f1ap_setup_response(const f1ap_setup_resp_t *a, const f1ap_setup_resp_t for (int j = 0; j < b->cells_to_activate[i].num_SI; j++) { const f1ap_sib_msg_t *a_SI_msg = &a->cells_to_activate[i].SI_msg[j]; const f1ap_sib_msg_t *b_SI_msg = &b->cells_to_activate[i].SI_msg[j]; - _EQ_CHECK_INT(*a_SI_msg->SI_container, *b_SI_msg->SI_container); - _EQ_CHECK_INT(a_SI_msg->SI_container_length, b_SI_msg->SI_container_length); + if (!eq_byte_array(&a_SI_msg->SI_container, &b_SI_msg->SI_container)) + return false; _EQ_CHECK_INT(a_SI_msg->SI_type, b_SI_msg->SI_type); } } @@ -1201,9 +1201,7 @@ f1ap_setup_resp_t cp_f1ap_setup_response(const f1ap_setup_resp_t *msg) for (int j = 0; j < cp_cell->num_SI; j++) { f1ap_sib_msg_t *cp_SI_msg = &cp_cell->SI_msg[j]; const f1ap_sib_msg_t *b_SI_msg = &msg_cell->SI_msg[j]; - cp_SI_msg->SI_container_length = b_SI_msg->SI_container_length; - cp_SI_msg->SI_container = malloc_or_fail(cp_SI_msg->SI_container_length); - memcpy(cp_SI_msg->SI_container, b_SI_msg->SI_container, b_SI_msg->SI_container_length); + cp_SI_msg->SI_container = copy_byte_array(b_SI_msg->SI_container); cp_SI_msg->SI_type = b_SI_msg->SI_type; } } @@ -1221,8 +1219,7 @@ void free_f1ap_setup_response(const f1ap_setup_resp_t *msg) free(msg->gNB_CU_name); for (int i = 0; i < msg->num_cells_to_activate; i++) 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_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container); free(msg->cells_to_activate); } @@ -1798,7 +1795,7 @@ void free_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t { for (int i = 0; i < msg->num_cells_to_activate; i++) 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_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container); free(msg->cells_to_activate); } @@ -1819,8 +1816,8 @@ bool eq_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *a for (int s = 0; s < a->cells_to_activate[i].num_SI; s++) { 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]; - _EQ_CHECK_INT(*a_sib_msg->SI_container, *b_sib_msg->SI_container); - _EQ_CHECK_INT(a_sib_msg->SI_container_length, b_sib_msg->SI_container_length); + if (!eq_byte_array(&a_sib_msg->SI_container, &b_sib_msg->SI_container)) + return false; _EQ_CHECK_INT(a_sib_msg->SI_type, b_sib_msg->SI_type); } } @@ -1842,9 +1839,7 @@ f1ap_gnb_cu_configuration_update_t cp_f1ap_cu_configuration_update(const f1ap_gn 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]; f1ap_sib_msg_t *SI_msg = &cp.cells_to_activate[i].SI_msg[s]; - SI_msg->SI_container = calloc_or_fail(SI_msg->SI_container_length, sizeof(*SI_msg->SI_container)); - for (int j = 0; j < SI_msg->SI_container_length; j++) - SI_msg->SI_container[j] = msg->cells_to_activate[i].SI_msg[s].SI_container[j]; + SI_msg->SI_container = copy_byte_array(msg->cells_to_activate[i].SI_msg[s].SI_container); } } return cp; @@ -2153,8 +2148,8 @@ bool eq_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration return false; _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++) { - _EQ_CHECK_INT(*a->cells_to_activate[i].SI_msg[s].SI_container, *b->cells_to_activate[i].SI_msg[s].SI_container); - _EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_container_length, b->cells_to_activate[i].SI_msg[s].SI_container_length); + if (!eq_byte_array(&a->cells_to_activate[i].SI_msg[s].SI_container, &b->cells_to_activate[i].SI_msg[s].SI_container)) + return false; _EQ_CHECK_INT(a->cells_to_activate[i].SI_msg[s].SI_type, b->cells_to_activate[i].SI_msg[s].SI_type); } } @@ -2180,9 +2175,7 @@ f1ap_gnb_du_configuration_update_acknowledge_t cp_f1ap_du_configuration_update_a f1ap_sib_msg_t *cp_sib = &cp.cells_to_activate[i].SI_msg[s]; const f1ap_sib_msg_t *msg_sib = &msg->cells_to_activate[i].SI_msg[s]; cp_sib->SI_type = msg_sib->SI_type; - cp_sib->SI_container_length = msg_sib->SI_container_length; - cp_sib->SI_container = calloc_or_fail(cp_sib->SI_container_length, sizeof(*cp_sib->SI_container)); - memcpy(cp_sib->SI_container, msg_sib->SI_container, cp_sib->SI_container_length); + cp_sib->SI_container = copy_byte_array(msg_sib->SI_container); } } return cp; @@ -2196,9 +2189,7 @@ void free_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configurati // SI_container for (int i = 0; i < msg->num_cells_to_activate; i++) { for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) { - if (msg->cells_to_activate[i].SI_msg[j].SI_container) { - free(msg->cells_to_activate[i].SI_msg[j].SI_container); - } + free_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container); } } free(msg->cells_to_activate); diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 8f315ff8ae..1140e3c39b 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -313,9 +313,9 @@ static void test_f1ap_setup_response(void) int SI_container_length = strlen(s) + 1; orig.cells_to_activate[0].num_SI = 1; f1ap_sib_msg_t *SI_msg = &orig.cells_to_activate[0].SI_msg[0]; - SI_msg->SI_container = malloc_or_fail(SI_container_length); - memcpy(SI_msg->SI_container, (uint8_t *)s, SI_container_length); - SI_msg->SI_container_length = SI_container_length; + SI_msg->SI_container.buf = malloc_or_fail(SI_container_length); + memcpy(SI_msg->SI_container.buf, s, SI_container_length); + SI_msg->SI_container.len = SI_container_length; SI_msg->SI_type = 7; } F1AP_F1AP_PDU_t *f1enc = encode_f1ap_setup_response(&orig); @@ -609,10 +609,10 @@ static void test_f1ap_du_configuration_update_acknowledge(void) 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; + orig.cells_to_activate[0].SI_msg[0].SI_container.len = 10; + orig.cells_to_activate[0].SI_msg[0].SI_container.buf = malloc(sizeof(uint8_t) * 10); + for (int i = 0; i < (int)orig.cells_to_activate[0].SI_msg[0].SI_container.len; i++) { + orig.cells_to_activate[0].SI_msg[0].SI_container.buf[i] = i; } // ASN.1 enc/dec F1AP_F1AP_PDU_t *f1enc = encode_f1ap_du_configuration_update_acknowledge(&orig); @@ -655,8 +655,8 @@ static void test_f1ap_cu_configuration_update(void) 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); + orig.cells_to_activate[0].SI_msg[0].SI_container.len = 10; + orig.cells_to_activate[0].SI_msg[0].SI_container.buf = 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); diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0c38993487..edbaae1735 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1083,7 +1083,7 @@ f1ap_gnb_du_system_info_t *get_sys_info(NR_BCCH_BCH_Message_t *mib, const NR_BCC nr_SIBs_t *si = (nr_SIBs_t *)seq_arr_at(du_SIBs, i); // other SIB in gNB-DU System Information not implemented yet // only DU SIB not included in this message is SIB19 - AssertFatal(si->SIB_type == 19, "Cannot handle SIB%d in gNB-DU System Information\n", si->SIB_type); + AssertFatal(si->SIB_type == NR_SIB_19, "Cannot handle SIB%d in gNB-DU System Information\n", (int)si->SIB_type); } } return sys_info; @@ -1243,7 +1243,7 @@ static seq_arr_t *fill_du_sibs(paramdef_t *GNBparamarray) int sib_value = GNBparamarray[GNB_DU_SIBS_IDX].iptr[i]; // SIB10, SIB12, SIB13, SIB14, SIB15, SIB17, SIB18, SIB19 and SIB20 are generated by the DU // all the rest in the DU - AssertFatal((sib_value > 9 && sib_value < 21) && sib_value != 11 && sib_value != 16, + AssertFatal((sib_value > NR_SIB_9 && sib_value < NR_SIB_21) && sib_value != NR_SIB_11 && sib_value != NR_SIB_16, "SIB%d is not a DU SIB, to be configured in DU section\n", sib_value); nr_SIBs_t *du_sib = calloc_or_fail(1, sizeof(nr_SIBs_t)); diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 202590f9fc..18d5bbb4a8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -988,22 +988,21 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ NR_SystemInformation_IEs_t *sysInfov17 = calloc(1, sizeof(*sysInfov17)); // for othersibs for (int i = 0; i < num_cu_sib; i++) { config_sibs[i] = cu_sib[i].SI_type; + const byte_array_t *container = &cu_sib[i].SI_container; + if (!container->buf || container->len == 0) { + LOG_W(NR_MAC, "SIB%d container is invalid (ptr=%p len=%zu), skipping\n", config_sibs[i], container->buf, container->len); + continue; + } switch (config_sibs[i]) { - case 2: { + case NR_SIB_2: { struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc(1, sizeof(*type)); type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; // SIB2 coming from CU need to decode it NR_SIB2_t *sib2 = NULL; - asn_dec_rval_t dec_rval = uper_decode(NULL, - &asn_DEF_NR_SIB2, - (void **)&sib2, - cu_sib[i].SI_container, - cu_sib[i].SI_container_length, - 0, - 0); + asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB2, (void **)&sib2, container->buf, container->len, 0, 0); if (dec_rval.code != RC_OK) { LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); - ASN_STRUCT_FREE(asn_DEF_NR_SIB2, cu_sib[i].SI_container); + ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2); } type->choice.sib2 = sib2; add_sib_to_systeminformation(sysInfo, type); @@ -1019,7 +1018,7 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ int sib_idx = i + num_cu_sib; config_sibs[sib_idx] = si->SIB_type; switch (config_sibs[sib_idx]) { - case 19: { + case NR_SIB_19: { struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type_du = calloc(1, sizeof(*type_du)); type_du->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700; NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 71da3f3d1b..495ad92c82 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -297,6 +297,17 @@ static void cp_f1_served_cell_info_to_cell(nr_rrc_cell_container_t *dst, const f } } +static void add_si_msg(served_cells_to_activate_t *cell, int sib_type, const byte_array_t *enc) +{ + AssertFatal(cell->num_SI < F1AP_MAX_NO_SIB_TYPES, + "Too many SI messages (%u), max supported is %u\n", + cell->num_SI, + F1AP_MAX_NO_SIB_TYPES); + cell->SI_msg[cell->num_SI].SI_container = copy_byte_array(*enc); + cell->SI_msg[cell->num_SI].SI_type = sib_type; + cell->num_SI++; +} + 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"); @@ -425,16 +436,13 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) // 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); + FOR_EACH_SEQ_ARR (nr_SIBs_t *, sib, sibs) { switch (sib->SIB_type) { - case 2: { + case NR_SIB_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++; + byte_array_t ba = {0}; + ba.len = do_SIB2_NR(&ba.buf, ssbmtc); + add_si_msg(&cell, sib->SIB_type, &ba); } break; default: AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); From 52d5705f473c2ea8d2a4cf1cb5bc5d339bb92a72 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 15:41:10 +0100 Subject: [PATCH 09/18] Neighbour config: log neighbour presence and counts during parsing Add info logs for neighbour cell configuration parsing without changing functional behaviour. --- openair2/GNB_APP/gnb_config.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index edbaae1735..6c5d9f4781 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2083,6 +2083,7 @@ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INS snprintf(path, sizeof(path), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx); GET_PARAMS_LIST(nlist, nparams, GNB_NEIGHBOUR_LIST_PARAM_LIST, GNB_CONFIG_STRING_NEIGHBOUR_LIST, path); + LOG_I(GNB_APP, "gNB %d: neighbour_list has %d serving cell(s)\n", gnb_idx, nlist.numelt); if (nlist.numelt < 1) return; @@ -2094,6 +2095,7 @@ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INS cell.nr_cell_id = *gpd(nlist.paramarray[elm], sizeofArray(nparams), GNB_CONFIG_STRING_NRCELLID)->u64ptr; snprintf(path, sizeof(path), "%s.[%i].%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_NEIGHBOUR_LIST, elm); GET_PARAMS_LIST(ncell_list, ncell_params, GNBNEIGHBOURCELLPARAMS_DESC, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, path); + LOG_I(GNB_APP, "Cell %ld has %d neighbours configured\n", cell.nr_cell_id, ncell_list.numelt); if (ncell_list.numelt < 1) continue; parse_neighbour_cells_list(&cell, &ncell_list, path, sizeofArray(ncell_params)); From 9afa94040dc98acaa2d8b376d4b1a71f902c36d8 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 10:33:56 +0100 Subject: [PATCH 10/18] RRC/SIB2: load cell reselection config from gNB Introduce a typed SIB2 cell reselection configuration, load it from the gNB configuration into the RRC gNB instance, and use it to build and encode SIB2 instead of relying on a fixed SIB2 definition. Validate SIB2 parameters against explicit bounds and make the SIB2 encoder consume a pre-built ASN.1 structure with safer error handling. Changes: - Add SIB2 mobility timer and q-HystSF enums plus SIB2 config structs (`sib2_speed_state_reselection_pars_t`, `sib2_config_t`) and a `sib2_config` field on `gNB_RRC_INST` in `nr_rrc_defs.h`. - Define SIB2 configuration option names, defaults (`GNB_CONFIG_STRING_SIB2_*`, `GNBSIB2PARAMS_DESC`) in `gnb_paramdef.h` for q_Hyst, thresholds, mobility timers, scaling factors and deriveSSB_IndexFromCell. - Add `sib2_config` parameter keys, defaults, and range/value checks in `gnb_paramdef.h` (`GNBSIB2PARAMS_DESC`). - Add SIB2 `fill_sib2_configuration()` in `gnb_config.c` to read SIB2 parameters from the config file log the resulting configuration and store it in `rrc->sib2_config` via `RCconfig_NRRRC()`. - Add `get_q_hyst_asn1()` and `get_sib2_from_cfg()` in `rrc_gNB_du.c`, and update `rrc_gNB_process_f1_setup_req()` to encode/add SIB2 from config with explicit encode-failure handling and logging. - Change `do_SIB2_NR` signature in `asn1_msg.h/.c` to `byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2)`, remove internal default SIB2 construction, and add ASN.1 constraint/encode error logging. --- openair2/GNB_APP/gnb_config.c | 61 +++++++++++++ openair2/GNB_APP/gnb_paramdef.h | 90 +++++++++++++++++++ openair2/RRC/NR/MESSAGES/asn1_msg.c | 48 ++++------ openair2/RRC/NR/MESSAGES/asn1_msg.h | 3 +- openair2/RRC/NR/nr_rrc_defs.h | 97 ++++++++++++++++++++ openair2/RRC/NR/rrc_gNB_du.c | 131 +++++++++++++++++++++++++++- 6 files changed, 395 insertions(+), 35 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 6c5d9f4781..5980666cff 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2171,6 +2171,64 @@ static void fill_measurement_configuration(uint8_t gnb_idx, gNB_RRC_INST *rrc) } } +/** @brief Parse and populate SIB2 configuration from config file + * @param[in] gnb_idx gNB index + * @return populated SIB2 configuration */ +static sib2_config_t fill_sib2_configuration(uint8_t gnb_idx) +{ + sib2_config_t sib2 = {0}; + + char sib2_path[MAX_OPTNAME_SIZE * 2 + 8]; + snprintf(sib2_path, sizeof(sib2_path), "%s.[%i].%s", GNB_CONFIG_STRING_GNB_LIST, gnb_idx, GNB_CONFIG_STRING_SIB2_CONFIG); + + GET_PARAMS(SIB2Params, GNBSIB2PARAMS_DESC, sib2_path); + const int n_sib2_params = sizeofArray(SIB2Params); + + cell_reselection_info_common_t *common = &sib2.cell_reselection_info_common; + common->q_Hyst = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_Q_HYST)->iptr; + /* speedStateReselectionPars */ + common->speedStateReselectionPars = calloc_or_fail(1, sizeof(*common->speedStateReselectionPars)); + sib2_speed_state_reselection_pars_t *speed = common->speedStateReselectionPars; + + /* Cell reselection serving frequency information */ + cell_reselection_serving_freq_info_t *serv = &sib2.cell_reselection_serving_freq_info; + serv->cellReselectionPriority = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_CELLRESEL_PRIORITY)->iptr; + serv->threshServingLowP = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_P)->iptr; + serv->threshServingLowQ = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_Q)->iptr; + serv->s_NonIntraSearchP = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_P)->iptr; + serv->s_NonIntraSearchQ = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_Q)->iptr; + + /* Intra-frequency cell reselection parameters */ + intra_freq_cell_reselection_info_t *intra = &sib2.intra_freq_cell_reselection_info; + intra->q_RxLevMin = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_Q_RXLEVMIN)->iptr; + intra->q_QualMin = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_Q_QUALMIN)->iptr; + intra->s_IntraSearchP = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_P)->iptr; + intra->s_IntraSearchQ = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_Q)->iptr; + intra->t_ReselectionNR = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_T_RESEL_NR)->iptr; + + /* deriveSSB_IndexFromCell (0/1) */ + int derive_ssb = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_DERIVE_SSB_IDX)->iptr; + sib2.deriveSSB_IndexFromCell = (derive_ssb != 0); + + /* Speed-state pars: optional parameters are omitted from the config file (default applied), not set to -1. */ + speed->t_Evaluation = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_T_EVAL)->iptr; + speed->t_HystNormal = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_T_HYST_NORMAL)->iptr; + speed->n_CellChangeMedium = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_MED)->iptr; + speed->n_CellChangeHigh = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_HIGH)->iptr; + speed->sf_Medium = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_SF_MEDIUM)->iptr; + speed->sf_High = *gpd(SIB2Params, n_sib2_params, GNB_CONFIG_STRING_SIB2_SPEED_SF_HIGH)->iptr; + + LOG_I(GNB_APP, + "gNB %d SIB2 config: q_Hyst=%d dB, cellReselPrio=%d, q_RxLevMin=%d dBm, t_ReselectionNR=%d s\n", + gnb_idx, + common->q_Hyst, + serv->cellReselectionPriority, + intra->q_RxLevMin, + intra->t_ReselectionNR); + + return sib2; +} + /** * @brief Allocates and initializes RRC instances * Currently assuming 1 instance @@ -2270,6 +2328,9 @@ gNB_RRC_INST *RCconfig_NRRRC() }// }//End for (k=0; k sib2_config = fill_sib2_configuration(i); }//End if (num_gnbs>0) if (!NODE_IS_DU(rrc->node_type)) diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 50184503ea..98e53d83e2 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -10,6 +10,7 @@ #define __GNB_APP_GNB_PARAMDEF__H__ #include "common/config/config_paramdesc.h" +#include "common/config/config_userapi.h" #include "common/ngran_types.h" #include "RRC_nr_paramsvalues.h" @@ -374,6 +375,95 @@ typedef enum { #define MEASUREMENT_EVENTS_INCLUDE_BEAM_MEAS_IDX 1 #define MEASUREMENT_EVENTS_MAX_RS_INDEX_TO_REPORT 2 +/*-------------------------------------------------------------------------------------------------------------------------------------*/ +/* SIB2 cell reselection configuration parameters */ +/*-------------------------------------------------------------------------------------------------------------------------------------*/ + +#define GNB_CONFIG_STRING_SIB2_CONFIG "sib2_config" + +/* clang-format off */ + +/* SIB2 top-level parameters (per cell) */ +#define GNB_CONFIG_STRING_SIB2_Q_HYST "q_Hyst" +#define GNB_CONFIG_STRING_SIB2_CELLRESEL_PRIORITY "cellReselectionPriority" +#define GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_P "threshServingLowP" +#define GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_Q "threshServingLowQ" +#define GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_P "s_NonIntraSearchP" +#define GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_Q "s_NonIntraSearchQ" +#define GNB_CONFIG_STRING_SIB2_Q_RXLEVMIN "q_RxLevMin" +#define GNB_CONFIG_STRING_SIB2_Q_QUALMIN "q_QualMin" +#define GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_P "s_IntraSearchP" +#define GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_Q "s_IntraSearchQ" +#define GNB_CONFIG_STRING_SIB2_T_RESEL_NR "t_ReselectionNR" +#define GNB_CONFIG_STRING_SIB2_DERIVE_SSB_IDX "deriveSSB_IndexFromCell" + +/* SIB2 speedStateReselectionPars parameters */ +#define GNB_CONFIG_STRING_SIB2_SPEED_T_EVAL "speed_t_Evaluation" +#define GNB_CONFIG_STRING_SIB2_SPEED_T_HYST_NORMAL "speed_t_HystNormal" +#define GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_MED "speed_n_CellChangeMedium" +#define GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_HIGH "speed_n_CellChangeHigh" +#define GNB_CONFIG_STRING_SIB2_SPEED_SF_MEDIUM "speed_sf_Medium" +#define GNB_CONFIG_STRING_SIB2_SPEED_SF_HIGH "speed_sf_High" + +#define GNBSIB2PARAMS_DESC { /* optname helpstr paramflags XXXptr def val type numelt */ \ + {GNB_CONFIG_STRING_SIB2_Q_HYST, \ + "SIB2 q-Hyst (0,1,2,3,4,5,6,8,10..24 dB)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}, 16}}}, \ + {GNB_CONFIG_STRING_SIB2_CELLRESEL_PRIORITY, \ + "SIB2 cellReselectionPriority (0..7)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 7}}}}, \ + {GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_P, \ + "SIB2 threshServingLowP (0..31)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \ + {GNB_CONFIG_STRING_SIB2_THRESH_SERVING_LOW_Q, \ + "SIB2 threshServingLowQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_P, \ + "SIB2 s-NonIntraSearchP (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_SIB2_S_NONINTRASEARCH_Q, \ + "SIB2 s-NonIntraSearchQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_SIB2_Q_RXLEVMIN, \ + "SIB2 q-RxLevMin (-70..-22)", 0, .iptr=NULL, .defintval=-56, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-70, -22}}}}, \ + {GNB_CONFIG_STRING_SIB2_Q_QUALMIN, \ + "SIB2 q-QualMin (-43..-12 or -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, \ + {-1, -43, -42, -41, -40, -39, -38, -37, -36, -35, -34, -33, -32, -31, -30, -29, -28, \ + -27, -26, -25, -24, -23, -22, -21, -20, -19, -18, -17, -16, -15, -14, -13, -12}, 33}}}, \ + {GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_P, \ + "SIB2 s-IntraSearchP (0..31)", 0, .iptr=NULL, .defintval=22, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \ + {GNB_CONFIG_STRING_SIB2_S_INTRASEARCH_Q, \ + "SIB2 s-IntraSearchQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_SIB2_T_RESEL_NR, \ + "SIB2 t-ReselectionNR (0..7)", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 7}}}}, \ + {GNB_CONFIG_STRING_SIB2_DERIVE_SSB_IDX, \ + "SIB2 deriveSSB-IndexFromCell (0/1)", PARAMFLAG_BOOL, .iptr=NULL, .defintval=1, TYPE_INT, 0}, \ + {GNB_CONFIG_STRING_SIB2_SPEED_T_EVAL, \ + "SIB2 speed t-Evaluation (0=s30,1=s60,2=s120,3=s180,4=s240)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 4}}}}, \ + {GNB_CONFIG_STRING_SIB2_SPEED_T_HYST_NORMAL, \ + "SIB2 speed t-HystNormal (0=s30,1=s60,2=s120,3=s180,4=s240)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 4}}}}, \ + {GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_MED, \ + "SIB2 speed n-CellChangeMedium (1..16)", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {1, 16}}}}, \ + {GNB_CONFIG_STRING_SIB2_SPEED_N_CELL_CHG_HIGH, \ + "SIB2 speed n-CellChangeHigh (1..16)", 0, .iptr=NULL, .defintval=2, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {1, 16}}}}, \ + {GNB_CONFIG_STRING_SIB2_SPEED_SF_MEDIUM, \ + "SIB2 speed sf-Medium (0=dB-6,1=dB-4,2=dB-2,3=dB0)", 0, .iptr=NULL, .defintval=1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 3}}}}, \ + {GNB_CONFIG_STRING_SIB2_SPEED_SF_HIGH, \ + "SIB2 speed sf-High (0=dB-6,1=dB-4,2=dB-2,3=dB0)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 3}}}}, \ +} +/* clang-format on */ + /* PLMN ID configuration */ #define GNB_CONFIG_STRING_PLMN_LIST "plmn_list" diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 8d5d00c975..b8cd1ab284 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -223,39 +223,25 @@ NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityR return NULL; } -//------------------------------------------------------------------------------ - -int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc) +byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2) { - NR_SIB2_t *sib2 = calloc(1, sizeof(*sib2)); - sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; - struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc(1, sizeof(*speed)); - NR_MobilityStateParameters_t mobilityStateParameters = {0}; - mobilityStateParameters.t_Evaluation = NR_MobilityStateParameters__t_Evaluation_s30; - mobilityStateParameters.t_HystNormal = NR_MobilityStateParameters__t_HystNormal_s30; - mobilityStateParameters.n_CellChangeMedium = 1; - mobilityStateParameters.n_CellChangeHigh = 2; - speed->mobilityStateParameters = mobilityStateParameters; - struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF qhyst = {0}; - qhyst.sf_Medium = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_Medium_dB_4; - qhyst.sf_High = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_High_dB_6; - speed->q_HystSF = qhyst; - sib2->cellReselectionInfoCommon.speedStateReselectionPars = speed; - sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 0; // INTEGER (0..7) - sib2->cellReselectionServingFreqInfo.threshServingLowP = 0; - NR_ReselectionThresholdQ_t *threshServingLowQ = calloc(1, sizeof(*threshServingLowQ)); - *threshServingLowQ = 0; - sib2->cellReselectionServingFreqInfo.threshServingLowQ = threshServingLowQ; - sib2->intraFreqCellReselectionInfo.q_RxLevMin = -56; // INTEGER (-70..-22) - sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 22; // INTEGER (0..31) - sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 1; // INTEGER (0..7) - sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true; - sib2->intraFreqCellReselectionInfo.smtc = ssbmtc; + byte_array_t msg = {.buf = NULL, .len = 0}; + char errbuf[256] = {0}; + size_t errlen = sizeof(errbuf); + int ret = asn_check_constraints(&asn_DEF_NR_SIB2, sib2, errbuf, &errlen); + if (ret != 0) { + LOG_E(NR_RRC, "SIB2 constraint check failed: %s\n", errbuf); + return msg; + } - ssize_t size = uper_encode_to_new_buffer(&asn_DEF_NR_SIB2, NULL, (void *)sib2, (void **)msg_SIB2); - AssertFatal (size > 0, "ASN1 message encoding failed (encoded %lu bytes)!\n", size); - ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2); - return size; + int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB2, NULL, (void *)sib2, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "Failed to encode SIB2\n"); + return msg; + } + + msg.len = val; + return msg; } int do_RRCReject(uint8_t *const buffer) diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 7aeb2b4ed0..43f4e7dcef 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -33,6 +33,7 @@ #include "NR_MeasurementTimingConfiguration.h" #include "NR_UE-NR-Capability.h" #include "NR_UE-CapabilityRAT-ContainerList.h" +#include "NR_SIB2.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" @@ -65,7 +66,7 @@ typedef struct { */ int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s *td, void *sptr); -int do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc); +byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2); int do_RRCReject(uint8_t *const buffer); diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 3b07ec6a6c..68104ba203 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -418,6 +418,101 @@ typedef struct nr_rrc_cuup_container_t { sctp_assoc_t assoc_id; } nr_rrc_cuup_container_t; +/**Timers for SIB2 MobilityStateParameters (TS 38.331) + * Value set shared by T-Evaluation and T-HystNormal: (s30, s60, s120, s180, s240). */ +typedef enum sib2_mobility_state_timer_e { + SIB2_MOBILITY_STATE_TIMER_S30, + SIB2_MOBILITY_STATE_TIMER_S60, + SIB2_MOBILITY_STATE_TIMER_S120, + SIB2_MOBILITY_STATE_TIMER_S180, + SIB2_MOBILITY_STATE_TIMER_S240, +} sib2_mobility_state_timer_t; + +/** Q-HystSF scaling factors for Q_hyst (TS 38.331) + * Speed-dependent offsets (-6, -4, -2, 0 dB) applied to the base Q_hyst + * when the UE is in medium or high mobility state. */ +typedef enum sib2_q_hystsf_e { + SIB2_Q_HYSTSF_DB_6, + SIB2_Q_HYSTSF_DB_4, + SIB2_Q_HYSTSF_DB_2, + SIB2_Q_HYSTSF_DB0, +} sib2_q_hystsf_t; + +/** SIB2 speedStateReselectionPars configuration + * Mirrors TS 38.331 MobilityStateParameters and Q-hystSF. */ +typedef struct sib2_speed_state_reselection_pars_s { + /* t-Evaluation: duration for evaluating allowed reselections + * to enter mobility states (s30, s60, s120, s180, s240) */ + sib2_mobility_state_timer_t t_Evaluation; + /* t-HystNormal: additional time period to evaluate reselection criteria + * before returning to normal mobility (s30, s60, s120, s180, s240) */ + sib2_mobility_state_timer_t t_HystNormal; + /* n-CellChangeMedium: max number of cell reselections to enter medium mobility state (1..16) */ + int n_CellChangeMedium; + /* n-CellChangeHigh: max number of cell reselections to enter high mobility state (1..16) */ + int n_CellChangeHigh; + /* sf-Medium: speed-dependent scaling factor for Qhyst in medium state (-6, -4, -2, 0 dB) */ + sib2_q_hystsf_t sf_Medium; + /* sf-High: speed-dependent scaling factor for Qhyst in high state (-6, -4, -2, 0 dB) */ + sib2_q_hystsf_t sf_High; +} sib2_speed_state_reselection_pars_t; + +/** SIB2 cellReselectionServingFreqInfo (TS 38.331) */ +typedef struct { + /* s-NonIntraSearchP: Srxlev threshold to trigger NR inter-freq / inter-RAT measurements (0..31)*2 dB. + * Set to -1 to omit (optional ASN.1 field). */ + int s_NonIntraSearchP; + /* s-NonIntraSearchQ: Squal threshold to trigger NR inter-freq / inter-RAT measurements (0..31)*2 dB */ + int s_NonIntraSearchQ; + /* threshServingLowP: Srxlev threshold (dB) used by the UE on the serving cell + * when reselecting towards a lower-priority RAT/frequency (0..31)*2 dB */ + int threshServingLowP; + /* threshServingLowQ: Squal threshold (dB) used by the UE on the serving cell + * when reselecting towards a lower-priority RAT/frequency (0..31)*2 dB. + * Set to -1 to omit (optional ASN.1 field). */ + int threshServingLowQ; + /* cellReselectionPriority: absolute priority of serving NR frequency (0..7) */ + int cellReselectionPriority; +} cell_reselection_serving_freq_info_t; + +/** SIB2 cellReselectionInfoCommon (TS 38.331) */ +typedef struct { + /* q-Hyst: hysteresis added to serving-cell ranking R_s (0..24 dB) */ + int q_Hyst; + /* speedStateReselectionPars: MobilityStateParameters + q-HystSF */ + sib2_speed_state_reselection_pars_t *speedStateReselectionPars; +} cell_reselection_info_common_t; + +/** SIB2 intraFreqCellReselectionInfo (TS 38.331) */ +typedef struct { + /* q-RxLevMin: minimum required RX level in the cell (dBm) */ + int q_RxLevMin; + /* q-QualMin: minimum required quality level in the cell (dB); */ + int q_QualMin; + /* s-IntraSearchP: Srxlev threshold for intra-freq meas (0..31) */ + int s_IntraSearchP; + /* s-IntraSearchQ: Squal threshold for intra-freq meas (0..31) */ + int s_IntraSearchQ; + /* t-ReselectionNR: NR cell reselection timer (0..7) */ + int t_ReselectionNR; +} intra_freq_cell_reselection_info_t; + +/** SIB2 configuration for idle/inactive cell reselection + * (maps to SIB2 reselection parameters in TS 38.304/38.331, per cell). + * @note Squal = Cell selection quality value (dB) + * Srxlev = Cell selection RX level value (dB) */ +typedef struct sib2_config_s { + // cellReselectionInfoCommon + cell_reselection_info_common_t cell_reselection_info_common; + // cellReselectionServingFreqInfo + cell_reselection_serving_freq_info_t cell_reselection_serving_freq_info; + // intraFreqCellReselectionInfo + intra_freq_cell_reselection_info_t intra_freq_cell_reselection_info; + /* deriveSSB_IndexFromCell: SIB2 deriveSSB-IndexFromCell - whether UE may + * assume SFN/SSB alignment across cells on serving freq (per TS 38.304/38.133) */ + bool deriveSSB_IndexFromCell; +} sib2_config_t; + //---NR---(completely change)--------------------- typedef struct gNB_RRC_INST_s { @@ -432,6 +527,8 @@ typedef struct gNB_RRC_INST_s { // RRC configuration nr_rrc_config_t configuration; seq_arr_t *SIBs; + // SIB2 configuration for cell reselection parameters + sib2_config_t sib2_config; // gNB N3 GTPU instance instance_t e1_inst; diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 495ad92c82..d5d0f27be9 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -23,6 +23,8 @@ #include "f1ap_messages_types.h" #include "ngap_messages_types.h" #include "nr_rrc_defs.h" +#include "NR_SIB2.h" +#include "openair2/RRC/NR/MESSAGES/asn1_msg.h" #include "rrc_gNB_mobility.h" #include "openair2/F1AP/f1ap_common.h" #include "openair2/F1AP/f1ap_ids.h" @@ -55,6 +57,121 @@ static NR_SSB_MTC_t *get_ssb_mtc(const NR_MeasurementTimingConfiguration_t *mtc) return ssb_mtc; } +/** @brief Map config q_Hyst (dB: 0,1,2,3,4,5,6,8,10..24) to ASN.1 q-Hyst enum index (0..15). + * ASN.1 NR_SIB2 cellReselectionInfoCommon.q-Hyst is encoded with 16 values. */ +static long get_q_hyst_asn1(int q_hyst_db) +{ + switch (q_hyst_db) { + case 0: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; + case 1: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB1; + case 2: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB2; + case 3: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB3; + case 4: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB4; + case 5: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB5; + case 6: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB6; + case 8: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB8; + case 10: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB10; + case 12: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB12; + case 14: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB14; + case 16: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB16; + case 18: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB18; + case 20: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB20; + case 22: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB22; + case 24: + return NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB24; + default: + AssertFatal(false, "unexpected q-Hyst value %d (configuration should have been validated)\n", q_hyst_db); + } +} + +/** @brief Build NR_SIB2 from validated SIB2 configuration. + * @param cfg Validated SIB2 configuration from config file + * @param ssbmtc SSB-MTC instance to attach to intraFreqCellReselectionInfo.smtc. */ +static NR_SIB2_t *get_sib2_from_cfg(const sib2_config_t *cfg, NR_SSB_MTC_t *ssbmtc) +{ + NR_SIB2_t *sib2 = calloc_or_fail(1, sizeof(*sib2)); + typeof(sib2->cellReselectionInfoCommon) *common = &sib2->cellReselectionInfoCommon; + const cell_reselection_info_common_t *cfg_common = &cfg->cell_reselection_info_common; + + /* q-Hyst: ASN.1 enum (0..15) converted from config dB (0,1,2,3,4,5,6,8,10..24). */ + common->q_Hyst = get_q_hyst_asn1(cfg_common->q_Hyst); + + /* speedStateReselectionPars */ + if (cfg_common->speedStateReselectionPars != NULL) { + struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc_or_fail(1, sizeof(*speed)); + NR_MobilityStateParameters_t mobilityStateParameters = {0}; + const sib2_speed_state_reselection_pars_t *pars = cfg_common->speedStateReselectionPars; + /* t-Evaluation / t-HystNormal: (0..7) */ + mobilityStateParameters.t_Evaluation = pars->t_Evaluation; + mobilityStateParameters.t_HystNormal = pars->t_HystNormal; + /* n-CellChangeMedium / n-CellChangeHigh: (1..16) */ + mobilityStateParameters.n_CellChangeMedium = pars->n_CellChangeMedium; + mobilityStateParameters.n_CellChangeHigh = pars->n_CellChangeHigh; + speed->mobilityStateParameters = mobilityStateParameters; + + /* q-HystSF: sf-Medium/sf-High (0..3) */ + struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF qhyst = {0}; + qhyst.sf_Medium = pars->sf_Medium; + qhyst.sf_High = pars->sf_High; + speed->q_HystSF = qhyst; + + common->speedStateReselectionPars = speed; + } + + /* cellReselectionServingFreqInfo */ + typeof(sib2->cellReselectionServingFreqInfo) *serv = &sib2->cellReselectionServingFreqInfo; + const cell_reselection_serving_freq_info_t *cfg_serv = &cfg->cell_reselection_serving_freq_info; + /* cellReselectionPriority: CellReselectionPriority (0..7) */ + serv->cellReselectionPriority = cfg_serv->cellReselectionPriority; + /* threshServingLowP: ReselectionThreshold (0..31) */ + serv->threshServingLowP = cfg_serv->threshServingLowP; + /* threshServingLowQ: ReselectionThresholdQ (0..31) */ + if (cfg_serv->threshServingLowQ != -1) + asn1cCallocOne(serv->threshServingLowQ, cfg_serv->threshServingLowQ); + /* s-NonIntraSearchP/Q: ReselectionThreshold/ReselectionThresholdQ (0..31) */ + if (cfg_serv->s_NonIntraSearchP != -1) + asn1cCallocOne(serv->s_NonIntraSearchP, cfg_serv->s_NonIntraSearchP); + if (cfg_serv->s_NonIntraSearchQ != -1) + asn1cCallocOne(serv->s_NonIntraSearchQ, cfg_serv->s_NonIntraSearchQ); + + /* intraFreqCellReselectionInfo */ + typeof(sib2->intraFreqCellReselectionInfo) *intra = &sib2->intraFreqCellReselectionInfo; + const intra_freq_cell_reselection_info_t *cfg_intra = &cfg->intra_freq_cell_reselection_info; + /* q-RxLevMin: Q-RxLevMin (-70..-22) */ + intra->q_RxLevMin = cfg_intra->q_RxLevMin; + /* q-QualMin: Q-QualMin (-43..-12) */ + if (cfg_intra->q_QualMin != -1) + asn1cCallocOne(intra->q_QualMin, cfg_intra->q_QualMin); + /* s-IntraSearchP: ReselectionThreshold (0..31) */ + intra->s_IntraSearchP = cfg_intra->s_IntraSearchP; + /* s-IntraSearchQ: ReselectionThresholdQ (0..31) */ + if (cfg_intra->s_IntraSearchQ != -1) + asn1cCallocOne(intra->s_IntraSearchQ, cfg_intra->s_IntraSearchQ); + /* t-ReselectionNR: (0..7) */ + intra->t_ReselectionNR = cfg_intra->t_ReselectionNR; + /* deriveSSB-IndexFromCell (bool) */ + intra->deriveSSB_IndexFromCell = cfg->deriveSSB_IndexFromCell; + /* smtc */ + intra->smtc = ssbmtc; + + return sib2; +} + /** @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) */ @@ -440,9 +557,17 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) switch (sib->SIB_type) { case NR_SIB_2: { NR_SSB_MTC_t *ssbmtc = get_ssb_mtc(new->mtc); - byte_array_t ba = {0}; - ba.len = do_SIB2_NR(&ba.buf, ssbmtc); - add_si_msg(&cell, sib->SIB_type, &ba); + NR_SIB2_t *sib2 = get_sib2_from_cfg(&rrc->sib2_config, ssbmtc); + byte_array_t enc = do_SIB2_NR(sib2); + ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2); + if (!enc.buf || enc.len == 0) { + free_byte_array(enc); + LOG_E(NR_RRC, "SIB2 encoding failed\n"); + break; + } + add_si_msg(&cell, sib->SIB_type, &enc); + free_byte_array(enc); + LOG_I(NR_RRC, "DU %ld: added SIB2 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); } break; default: AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); From c53b28f51dcd30cf8e501c84e7df2d225cfdfc35 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 3 Mar 2026 14:10:24 +0100 Subject: [PATCH 11/18] RRC: add SIB3/SIB4 neighbour offsets and inter-frequency SIB4 config Introduce typed SIB3/SIB4 neighbour and inter-frequency carrier configuration structures, wire them into the RRC neighbour and gNB instances, and centralise parsing and bounds checking of per-frequency SIB4 parameters from the gNB frequency list. Changes: - Define SIB3/SIB4 reselection and offset bound macros in gnb_config.c and apply them when parsing SIB4 frequency_config and per-carrier q_RxLevMin/t_ReselectionNR. - Add add_inter_freq and parse_inter_freq_list helpers in gnb_config.c to populate a shared inter_freqs seq_arr_t from gNB_CONFIG_STRING_GNB_LIST frequency_list entries, enforcing unique (ARFCN,SCS) combinations and logging duplicates. - Extend neighbour cell parameters in gnb_paramdef.h with SIB3 per- neighbour offset fields and define frequency_list/frequency_config option names, descriptor tables, and index constants for SIB4 inter- frequency configuration. - Introduce nr_neighbour_cell_neighbor_offset_t, nr_neighbour_cell_sib3_t, nr_neighbour_cell_sib4_freq_t, nr_neighbour_cell_sib4_t, and nr_inter_freq_cfg_t in nr_rrc_defs.h, and extend nr_neighbour_cell_t and gNB_RRC_INST with sib3/sib4 and inter_freqs members respectively. - udpated fill_neighbour_cell_configuration to use GET_PARAMS_LIST with params bound check --- openair2/GNB_APP/gnb_config.c | 110 ++++++++++++++++++++++++++++++++ openair2/GNB_APP/gnb_paramdef.h | 78 ++++++++++++++++++++++ openair2/RRC/NR/nr_rrc_defs.h | 73 +++++++++++++++++++++ 3 files changed, 261 insertions(+) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 5980666cff..856078e6f7 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -2012,6 +2012,108 @@ static nr_neighbour_cell_t *get_neighbour_by_pci(seq_arr_t *neighbour_cells, int return NULL; } +/** @brief Parse gNB-level inter-frequency list (frequency_list) into a shared array. + * This parses gNBs.[gnb_idx].frequency_list once and validates per-frequency SIB4 + * parameters. The resulting list can then be reused for all serving cells. + * @param[out] inter_freqs Sequence array of nr_inter_freq_cfg_t to populate + * @param[in] gnb_idx gNB index */ +static bool eq_inter_freq_arfcn_scs(const void *vval, const void *vit) +{ + const nr_inter_freq_cfg_t *key = (const nr_inter_freq_cfg_t *)vval; + const nr_inter_freq_cfg_t *cur = (const nr_inter_freq_cfg_t *)vit; + return key->arfcn == cur->arfcn && key->scs == cur->scs; +} + +/** @brief Insert an inter-frequency config if (ARFCN,SCS) is unique in the list. + * Fails fast if a duplicate is found. + * @param[in,out] inter_freqs Existing inter-frequency list + * @param[in] f Candidate entry to insert (by value) + * @param[in] gnb_idx gNB index for logging + */ +static void add_inter_freq(seq_arr_t *inter_freqs, const nr_inter_freq_cfg_t *f, uint8_t gnb_idx) +{ + const nr_inter_freq_cfg_t key = {.arfcn = f->arfcn, .scs = f->scs}; + elm_arr_t dup = find_if(inter_freqs, (void *)&key, eq_inter_freq_arfcn_scs); + if (dup.found) { + AssertFatal(false, "gNB %d: duplicate inter-freq carrier ARFCN %d, SCS %d in frequency_list\n", gnb_idx, f->arfcn, f->scs); + } + + seq_arr_push_back(inter_freqs, (void *)f, sizeof(*f)); +} + +static void parse_inter_freq_list(seq_arr_t *inter_freqs, const uint8_t gnb_idx) +{ + seq_arr_init(inter_freqs, sizeof(nr_inter_freq_cfg_t)); + + char freq_path[MAX_OPTNAME_SIZE + 8]; + snprintf(freq_path, sizeof(freq_path), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, gnb_idx); + GET_PARAMS_LIST(freq_list, freq_params, GNBFREQUENCYPARAMS_DESC, GNB_CONFIG_STRING_FREQUENCY_LIST, freq_path); + + for (int i = 0; i < freq_list.numelt; ++i) { + const paramdef_t *freq_item = freq_list.paramarray[i]; + nr_inter_freq_cfg_t f = {0}; + f.arfcn = *gpd(freq_item, sizeofArray(freq_params), GNB_CONFIG_STRING_FREQUENCY_ABS_FREQ_SSB)->i64ptr; + f.scs = *gpd(freq_item, sizeofArray(freq_params), GNB_CONFIG_STRING_FREQUENCY_SCS)->uptr; + + char cfg_path[MAX_OPTNAME_SIZE + 16]; + snprintf(cfg_path, + sizeof(cfg_path), + "%s.[%i].%s.[%i]", + GNB_CONFIG_STRING_GNB_LIST, + gnb_idx, + GNB_CONFIG_STRING_FREQUENCY_LIST, + i); + GET_PARAMS(freq_cfg, GNBFREQUENCYCONFIGPARAMS_DESC, cfg_path); + + nr_neighbour_cell_sib4_freq_t *p = &f.freq_cfg; + + p->cellReselectionPriority = *freq_cfg[GNB_CONFIG_FREQUENCY_CELL_RESEL_PRIO_IDX].iptr; + p->threshX_HighP = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_P_IDX].iptr; + p->threshX_LowP = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_LOW_P_IDX].iptr; + p->threshX_HighQ = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_Q_IDX].iptr; + p->threshX_LowQ = *freq_cfg[GNB_CONFIG_FREQUENCY_THRESH_X_LOW_Q_IDX].iptr; + p->q_OffsetFreq = *freq_cfg[GNB_CONFIG_FREQUENCY_Q_OFFSET_FREQ_IDX].iptr; + f.q_RxLevMin = *freq_cfg[GNB_CONFIG_FREQUENCY_Q_RXLEVMIN_IDX].iptr; + f.t_ReselectionNR = *freq_cfg[GNB_CONFIG_FREQUENCY_T_RESEL_NR_IDX].uptr; + + /* Add inter-frequency configuration to the list. */ + add_inter_freq(inter_freqs, &f, gnb_idx); + + LOG_I(GNB_APP, + "gNB %d: SIB4 inter-freq ARFCN %d, SCS %d, prio=%d, ThreshX_HighP=%d, ThreshX_LowP=%d, ThreshX_HighQ=%d, " + "ThreshX_LowQ=%d, q_OffsetFreq=%d\n", + gnb_idx, + f.arfcn, + f.scs, + f.freq_cfg.cellReselectionPriority, + f.freq_cfg.threshX_HighP, + f.freq_cfg.threshX_LowP, + f.freq_cfg.threshX_HighQ, + f.freq_cfg.threshX_LowQ, + f.freq_cfg.q_OffsetFreq); + } +} + +static void set_sib3_offsets(nr_neighbour_cell_t *n, const paramdef_t *cell_params, int n_cell_params) +{ + nr_neighbour_cell_neighbor_offset_t *sib3_off = &n->sib3.offset; + + /* SIB3 per-neighbour offsets */ + sib3_off->q_OffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL)->iptr; + sib3_off->q_RxLevMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL)->iptr; + sib3_off->q_QualMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL)->iptr; +} + +static void set_sib4_offsets(nr_neighbour_cell_t *n, const paramdef_t *cell_params, int n_cell_params) +{ + nr_neighbour_cell_neighbor_offset_t *sib4_off = &n->sib4.offset; + + /* SIB4 per-neighbour offsets */ + sib4_off->q_OffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL)->iptr; + sib4_off->q_RxLevMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL)->iptr; + sib4_off->q_QualMinOffsetCell = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL)->iptr; +} + /** @brief Parse and add neighbour cells for a given cell configuration * @param[in,out] cell Neighbour cell configuration to populate * @param[in] list Parameter list describing neighbour cells @@ -2036,6 +2138,11 @@ static void parse_neighbour_cells_list(neighbour_cell_configuration_t *cell, .absoluteFrequencySSB = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_CELL_ABS_FREQ_SSB)->i64ptr, .tac = *gpd(cell_params, n_cell_params, GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE)->uptr}; + set_sib3_offsets(&n, cell_params, n_cell_params); + set_sib4_offsets(&n, cell_params, n_cell_params); + /* SIB4 per-frequency reselection parameters (q_OffsetFreq, thresholds, priority) + * are populated in parse_inter_freq_list() per-frequency, not per neighbour here. */ + char p[CONFIG_MAXOPTLENGTH]; // plmn path snprintf(p, sizeof(p), "%s.%s.[%i].%s", n_path, GNB_CONFIG_STRING_NEIGHBOUR_CELL_LIST, l, GNB_CONFIG_STRING_NEIGHBOUR_PLMN); GET_PARAMS(NeighbourPlmn, GNBPLMNPARAMS_DESC, p); @@ -2087,6 +2194,9 @@ static void fill_neighbour_cell_configuration(const uint8_t gnb_idx, gNB_RRC_INS if (nlist.numelt < 1) return; + /* Parse gNB-level inter-frequency configuration once and reuse it for all cells. */ + parse_inter_freq_list(&rrc->inter_freqs, gnb_idx); + rrc->neighbour_cell_configuration = malloc_or_fail(sizeof(seq_arr_t)); seq_arr_init(rrc->neighbour_cell_configuration, sizeof(neighbour_cell_configuration_t)); diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 98e53d83e2..35a1c096d1 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -300,6 +300,11 @@ typedef enum { #define GNB_CONFIG_STRING_NEIGHBOUR_CELL_BAND "band" #define GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE "tracking_area_code" #define GNB_CONFIG_STRING_NEIGHBOUR_PLMN "plmn" +/* SIB3 fields (intra-frequency) - per-neighbor */ +#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL "q_OffsetCell" +#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL "q_RxLevMinOffsetCell" +#define GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL "q_QualMinOffsetCell" +/* SIB4 per-frequency fields are configured in frequency_list.frequency_config */ // clang-format off #define GNBNEIGHBOURCELLPARAMS_DESC { /* optname helpstr paramflags XXXptr def val type numelt */ \ @@ -324,9 +329,82 @@ typedef enum { {GNB_CONFIG_STRING_NEIGHBOUR_TRACKING_ARE_CODE, \ "neighbour cell tracking area (0..16777215)", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 16777215}}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_OFFSET_CELL, \ + "Q-OffsetCell for SIB3/SIB4 (Q-OffsetRange, default 0)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, \ + {-24, -22, -20, -18, -16, -14, -12, -10, -8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}, 31}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_RXLEVMIN_OFFSET_CELL, \ + "Q-RxLevMinOffsetCell for SIB3/SIB4 (1..8 or -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {-1, 1, 2, 3, 4, 5, 6, 7, 8}, 9}}}, \ + {GNB_CONFIG_STRING_NEIGHBOUR_CELL_Q_QUALMIN_OFFSET_CELL, \ + "Q-QualMinOffsetCell for SIB3/SIB4 (1..8 or -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {-1, 1, 2, 3, 4, 5, 6, 7, 8}, 9}}}, \ } // clang-format on +/* Per-frequency neighbour configuration (frequency_list) */ +#define GNB_CONFIG_STRING_FREQUENCY_LIST "frequency_list" +#define GNB_CONFIG_STRING_FREQUENCY_ABS_FREQ_SSB "absoluteFrequencySSB" +#define GNB_CONFIG_STRING_FREQUENCY_SCS "subcarrierSpacing" +#define GNB_CONFIG_STRING_FREQUENCY_BAND "band" +#define GNB_CONFIG_STRING_FREQUENCY_CONFIG "frequency_config" + +#define GNB_CONFIG_STRING_FREQUENCY_CELL_RESEL_PRIO "cellReselectionPriority" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_P "threshX_HighP" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_P "threshX_LowP" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_Q "threshX_HighQ" +#define GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_Q "threshX_LowQ" +#define GNB_CONFIG_STRING_FREQUENCY_Q_OFFSET_FREQ "q_OffsetFreq" +#define GNB_CONFIG_STRING_FREQUENCY_Q_RXLEVMIN "q_RxLevMin" +#define GNB_CONFIG_STRING_FREQUENCY_T_RESEL_NR "t_ReselectionNR" + +// clang-format off +#define GNBFREQUENCYPARAMS_DESC { \ +/* optname helpstr paramflags XXXptr def val type numelt */ \ + {GNB_CONFIG_STRING_FREQUENCY_ABS_FREQ_SSB, "inter-frequency abs freq ssb", PARAMFLAG_MANDATORY, .i64ptr=NULL, .defint64val=0, TYPE_INT64, 0}, \ + {GNB_CONFIG_STRING_FREQUENCY_SCS, "inter-frequency scs", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ + {GNB_CONFIG_STRING_FREQUENCY_BAND, "inter-frequency band", PARAMFLAG_MANDATORY, .uptr=NULL, .defuintval=78, TYPE_UINT, 0}, \ +} + +#define GNBFREQUENCYCONFIGPARAMS_DESC { \ + {GNB_CONFIG_STRING_FREQUENCY_CELL_RESEL_PRIO, \ + "SIB4 cellReselectionPriority (0..7, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 7}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_P, \ + "SIB4 threshX-HighP (0..31)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_P, \ + "SIB4 threshX-LowP (0..31)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {0, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_HIGH_Q, \ + "SIB4 threshX-HighQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_THRESH_X_LOW_Q, \ + "SIB4 threshX-LowQ (0..31, -1=disabled)", 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-1, 31}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_Q_OFFSET_FREQ, \ + "SIB4 q-OffsetFreq (Q-OffsetRange values, default 0)", 0, .iptr=NULL, .defintval=0, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s1 = {config_check_intval, {-24, -22, -20, -18, -16, -14, -12, -10, \ + -8, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24}, 31}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_Q_RXLEVMIN, \ + "SIB4 per-carrier Q-RxLevMin (-70..-22, default -22)", 0, .iptr=NULL, .defintval=-22, TYPE_INT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_intrange, {-70, -22}}}}, \ + {GNB_CONFIG_STRING_FREQUENCY_T_RESEL_NR, \ + "SIB4 per-carrier t-ReselectionNR (0..7, default 0)", 0, .uptr=NULL, .defuintval=0, TYPE_UINT, 0, \ + .chkPptr = &(checkedparam_t){.s2 = {config_check_uintrange, {0, 7}}}}, \ +} +// clang-format on + +/* Indexes into GNBFREQUENCYCONFIGPARAMS_DESC */ +#define GNB_CONFIG_FREQUENCY_CELL_RESEL_PRIO_IDX 0 +#define GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_P_IDX 1 +#define GNB_CONFIG_FREQUENCY_THRESH_X_LOW_P_IDX 2 +#define GNB_CONFIG_FREQUENCY_THRESH_X_HIGH_Q_IDX 3 +#define GNB_CONFIG_FREQUENCY_THRESH_X_LOW_Q_IDX 4 +#define GNB_CONFIG_FREQUENCY_Q_OFFSET_FREQ_IDX 5 +#define GNB_CONFIG_FREQUENCY_Q_RXLEVMIN_IDX 6 +#define GNB_CONFIG_FREQUENCY_T_RESEL_NR_IDX 7 + /* New Measurement Configurations*/ #define GNB_CONFIG_STRING_MEASUREMENT_CONFIGURATION "nr_measurement_configuration" diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 68104ba203..f0e212c79f 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -312,6 +312,72 @@ typedef struct { bool is_default_a3_configuration_exists; } nr_measurement_configuration_t; +/** @brief Per-neighbor cell-specific offsets (TS 38.331), shared by SIB3 and SIB4 + * Maps to ASN.1 IntraFreqNeighCellInfo and InterFreqNeighCellInfo + * (SIB3.IntraFreqNeighCellList, SIB4.InterFreqCarrierFreqInfo.neighCellList) */ +typedef struct { + // q-OffsetCell: (Q-OffsetRange values -24,-22,-20,-18,-16,-14,-12,-10,-8,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,8,10,12,14,16,18,20,22,24 dB) + // per-neighbor cell ranking + int q_OffsetCell; + // q-RxLevMinOffsetCell: Q-OffsetCellSmall (1..8): Per-neighbor RSRP minimum offset + int q_RxLevMinOffsetCell; + // q-QualMinOffsetCell: Q-OffsetCellSmall (1..8): Per-neighbor RSRQ minimum offset + int q_QualMinOffsetCell; +} nr_neighbour_cell_neighbor_offset_t; + +/** @brief SIB3 (intra-frequency) neighbor list parameters (TS 38.331) + * Per-neighbor fields for SIB3.IntraFreqNeighCellList.IntraFreqNeighCellInfo. + * SIB3 carries intra-freq neighbor offsets for ranking. */ +typedef struct { + nr_neighbour_cell_neighbor_offset_t offset; +} nr_neighbour_cell_sib3_t; + +/** @brief SIB4 inter-frequency carrier parameters (TS 38.331) + * Per-frequency fields for SIB4.InterFreqCarrierFreqList.InterFreqCarrierFreqInfo. + * Stored once per ARFCN; all neighbors on that frequency share this config. */ +typedef struct { + // cellReselectionPriority (0..7): Absolute priority of this inter-freq carrier + int cellReselectionPriority; + // threshX-HighP (0..31): RSRP threshold for reselection to a higher-priority inter-freq + int threshX_HighP; + // threshX-LowP (0..31): RSRP threshold for reselection to a lower-priority inter-freq + int threshX_LowP; + // threshX-HighQ (0..31): RSRQ threshold for higher-priority inter-freq reselection + int threshX_HighQ; + // threshX-LowQ (0..31): RSRQ threshold for lower-priority inter-freq reselection + int threshX_LowQ; + // q-OffsetFreq (Q-OffsetRange values: -24,-22,-20,-18,-16,-14,-12,-10,-8,-6,-5,-4,-3,-2,-1,0,1,2,3,4,5,6,8,10,12,14,16,18,20,22,24 dB): + // Frequency-specific offset in inter-freq cell ranking formula + int q_OffsetFreq; +} nr_neighbour_cell_sib4_freq_t; + +/** @brief SIB4 (inter-frequency) parameters per neighbor (TS 38.331) + * Combines per-frequency carrier info (InterFreqCarrierFreqInfo) and per-neighbor + * offsets (InterFreqNeighCellInfo). */ +typedef struct { + // Per-frequency carrier (InterFreqCarrierFreqInfo) + nr_neighbour_cell_sib4_freq_t sib4_freq; + // Per-neighbor offsets (InterFreqNeighCellInfo) + nr_neighbour_cell_neighbor_offset_t offset; +} nr_neighbour_cell_sib4_t; + +/** @brief Per-frequency SIB4 configuration (TS 38.331), keyed by ARFCN + * One entry per inter-freq carrier (absoluteFrequencySSB + subcarrierSpacing). + * freq_cfg holds InterFreqCarrierFreqInfo parameters. Analogous to one “carrier” slice of SIB4 + * interFreqCarrierFreqList. */ +typedef struct { + // absoluteFrequencySSB (ARFCN) + int arfcn; + // ssbSubcarrierSpacing + int scs; + // q-RxLevMin (-70..-22 dBm) + int q_RxLevMin; + // t-ReselectionNR (0..7) + int t_ReselectionNR; + // Inter-frequency carrier parameters (InterFreqCarrierFreqInfo) + nr_neighbour_cell_sib4_freq_t freq_cfg; +} nr_inter_freq_cfg_t; + typedef struct { uint32_t gNB_ID; uint64_t nrcell_id; @@ -322,6 +388,10 @@ typedef struct { plmn_id_t plmn; uint32_t tac; bool isIntraFrequencyNeighbour; + // SIB3 (intra-frequency neighbor cell-specific offsets) + nr_neighbour_cell_sib3_t sib3; + // SIB4 (inter-frequency neighbor cell-specific parameters) + nr_neighbour_cell_sib4_t sib4; } nr_neighbour_cell_t; typedef struct neighbour_cell_configuration_s { @@ -540,6 +610,9 @@ typedef struct gNB_RRC_INST_s { nr_mac_rrc_dl_if_t mac_rrc; cucp_cuup_if_t cucp_cuup; + // Per-frequency SIB4 configurations, indexed by ARFCN + seq_arr_t inter_freqs; /* array of nr_inter_freq_cfg_t */ + // Per-cell neighbour configurations, indexed by cell_id seq_arr_t *neighbour_cell_configuration; nr_measurement_configuration_t measurementConfiguration; From 817d1a6cb5c15bf2cc747e13ffee748ccddc8dd7 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 10:51:18 +0100 Subject: [PATCH 12/18] RRC/MAC: add SIB3 intra-freqeuncy neighbour support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce NR SIB3 encoding in RRC and wire SIB3 neighbour information into CU–DU F1 Setup and DU SystemInformation. Extend MAC to decode NR SIB3 from CU-provided SIB containers and attach the decoded SIB3 to the DU SystemInformation structure. Changes: - Add NR_SIB3 ASN.1 include and a do_SIB3_NR() encoder in asn1_msg so SIB3 can be serialized into a byte_array_t buffer. - Introduce get_q_offset_asn1() and get_sib3_intra_freq_neighbors() in rrc_gNB_du.c to build an intra-frequency neighbour cell list for SIB3, mapping q_OffsetCell dB values to NR_Q_OffsetRange and optionally filling q_RxLevMinOffsetCell and q_QualMinOffsetCell. - Limit the number of intra-frequency neighbours in SIB3 to NR_maxCellIntra, logging a warning and skipping additional cells when the limit is reached. - Hook SIB3 construction and encoding into rrc_gNB_process_f1_setup_req() so SIB type 3 entries are added to the F1 Setup Response when neighbour configuration and matching neighbours are available. - Handle NR_SIB_3 in nr_mac_configure_other_sib() by validating the CU SIB3 container buffer, decoding it with uper_decode into an NR_SIB3_t, and attaching the resulting SIB3 to the MAC SystemInformation via add_sib_to_systeminformation(). --- openair2/LAYER2/NR_MAC_gNB/config.c | 15 +++ openair2/RRC/NR/MESSAGES/asn1_msg.c | 21 ++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 + openair2/RRC/NR/rrc_gNB_du.c | 156 ++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index 18d5bbb4a8..f38eebd326 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1008,6 +1008,21 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ add_sib_to_systeminformation(sysInfo, type); break; } + case NR_SIB_3: { + struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc_or_fail(1, sizeof(*type)); + type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib3; + NR_SIB3_t *sib3 = NULL; + asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB3, (void **)&sib3, container->buf, container->len, 0, 0); + if (dec_rval.code != RC_OK) { + LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); + ASN_STRUCT_FREE(asn_DEF_NR_SIB3, sib3); + free(type); + break; + } + type->choice.sib3 = sib3; + add_sib_to_systeminformation(sysInfo, type); + break; + } default : AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]); } diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index b8cd1ab284..80235ebdb7 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -244,6 +244,27 @@ byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2) return msg; } +byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3) +{ + byte_array_t msg = {.buf = NULL, .len = 0}; + char errbuf[256] = {0}; + size_t errlen = sizeof(errbuf); + int ret = asn_check_constraints(&asn_DEF_NR_SIB3, sib3, errbuf, &errlen); + if (ret != 0) { + LOG_E(NR_RRC, "SIB3 constraint check failed: %s\n", errbuf); + return msg; + } + + int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB3, NULL, (void *)sib3, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "Failed to encode SIB3\n"); + return msg; + } + + msg.len = val; + return msg; +} + int do_RRCReject(uint8_t *const buffer) { asn_enc_rval_t enc_rval; diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 43f4e7dcef..11963780f1 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -34,6 +34,7 @@ #include "NR_UE-NR-Capability.h" #include "NR_UE-CapabilityRAT-ContainerList.h" #include "NR_SIB2.h" +#include "NR_SIB3.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" @@ -67,6 +68,7 @@ typedef struct { int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s *td, void *sptr); byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2); +byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3); int do_RRCReject(uint8_t *const buffer); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index d5d0f27be9..43692ec742 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -24,6 +24,8 @@ #include "ngap_messages_types.h" #include "nr_rrc_defs.h" #include "NR_SIB2.h" +#include "NR_SIB3.h" +#include "NR_Q-OffsetRange.h" #include "openair2/RRC/NR/MESSAGES/asn1_msg.h" #include "rrc_gNB_mobility.h" #include "openair2/F1AP/f1ap_common.h" @@ -35,6 +37,7 @@ #include "s1ap_messages_types.h" #include "tree.h" #include "uper_decoder.h" +#include "common/utils/oai_asn1.h" #include "utils.h" #include "xer_encoder.h" @@ -172,6 +175,136 @@ static NR_SIB2_t *get_sib2_from_cfg(const sib2_config_t *cfg, NR_SSB_MTC_t *ssbm return sib2; } +/** @brief Map internal int enum to ASN.1 Q-OffsetRange value. */ +static long get_q_offset_asn1(int q_offset_db) +{ + switch (q_offset_db) { + case -24: + return NR_Q_OffsetRange_dB_24; + case -22: + return NR_Q_OffsetRange_dB_22; + case -20: + return NR_Q_OffsetRange_dB_20; + case -18: + return NR_Q_OffsetRange_dB_18; + case -16: + return NR_Q_OffsetRange_dB_16; + case -14: + return NR_Q_OffsetRange_dB_14; + case -12: + return NR_Q_OffsetRange_dB_12; + case -10: + return NR_Q_OffsetRange_dB_10; + case -8: + return NR_Q_OffsetRange_dB_8; + case -6: + return NR_Q_OffsetRange_dB_6; + case -5: + return NR_Q_OffsetRange_dB_5; + case -4: + return NR_Q_OffsetRange_dB_4; + case -3: + return NR_Q_OffsetRange_dB_3; + case -2: + return NR_Q_OffsetRange_dB_2; + case -1: + return NR_Q_OffsetRange_dB_1; + case 0: + return NR_Q_OffsetRange_dB0; + case 1: + return NR_Q_OffsetRange_dB1; + case 2: + return NR_Q_OffsetRange_dB2; + case 3: + return NR_Q_OffsetRange_dB3; + case 4: + return NR_Q_OffsetRange_dB4; + case 5: + return NR_Q_OffsetRange_dB5; + case 6: + return NR_Q_OffsetRange_dB6; + case 8: + return NR_Q_OffsetRange_dB8; + case 10: + return NR_Q_OffsetRange_dB10; + case 12: + return NR_Q_OffsetRange_dB12; + case 14: + return NR_Q_OffsetRange_dB14; + case 16: + return NR_Q_OffsetRange_dB16; + case 18: + return NR_Q_OffsetRange_dB18; + case 20: + return NR_Q_OffsetRange_dB20; + case 22: + return NR_Q_OffsetRange_dB22; + case 24: + return NR_Q_OffsetRange_dB24; + default: + AssertFatal(false, "Invalid q_offset_db value %d\n", q_offset_db); + } +} + +/** @brief Build a SIB3 (intra-frequency neighbour cell list) from the configured neighbour cells. + * Only neighbours whose SSB ARFCN matches @param serving_ssb_arfcn are included. + * @param neighbours list of configured neighbour cells + * @param serving_ssb_arfcn SSB ARFCN of the serving cell, used to filter intra-frequency neighbours + * @returns allocated NR_SIB3_t on success, NULL if no matching neighbours were found */ +static NR_SIB3_t *get_sib3_intra_freq_neighbors(const seq_arr_t *neighbours, uint32_t serving_ssb_arfcn) +{ + DevAssert(neighbours); + + NR_SIB3_t *sib3 = calloc_or_fail(1, sizeof(*sib3)); + sib3->intraFreqNeighCellList = calloc_or_fail(1, sizeof(*sib3->intraFreqNeighCellList)); + + int intra_count = 0; + FOR_EACH_SEQ_ARR (const nr_neighbour_cell_t *, neigh, neighbours) { + if (neigh->absoluteFrequencySSB != serving_ssb_arfcn) + continue; + + LOG_D(NR_RRC, + "SIB3: intra-frequency neighbour candidate: cell ID %lu, PCI %d (SSB ARFCN %d)\n", + neigh->nrcell_id, + neigh->physicalCellId, + neigh->absoluteFrequencySSB); + + NR_IntraFreqNeighCellInfo_t *cell = calloc_or_fail(1, sizeof(*cell)); + cell->physCellId = neigh->physicalCellId; + + const nr_neighbour_cell_neighbor_offset_t *off = &neigh->sib3.offset; + /* q-OffsetCell: NR_Q-OffsetRange (-24..24 dB), 0 dB default */ + cell->q_OffsetCell = get_q_offset_asn1(off->q_OffsetCell); + + /* q-RxLevMinOffsetCell / q-QualMinOffsetCell: (1..8) */ + if (off->q_RxLevMinOffsetCell != -1) + asn1cCallocOne(cell->q_RxLevMinOffsetCell, off->q_RxLevMinOffsetCell); + if (off->q_QualMinOffsetCell != -1) + asn1cCallocOne(cell->q_QualMinOffsetCell, off->q_QualMinOffsetCell); + + /* Add to SIB3 intra-frequency neighbour list */ + if (sib3->intraFreqNeighCellList->list.count >= NR_maxCellIntra) { + LOG_W(NR_RRC, "SIB3: too many intra-frequency neighbors (max %d), skipping cell %ld\n", NR_maxCellIntra, neigh->nrcell_id); + asn1cFreeStruc(asn_DEF_NR_IntraFreqNeighCellInfo, cell); + break; + } + asn1cSeqAdd(&sib3->intraFreqNeighCellList->list, cell); + ++intra_count; + } + + if (intra_count == 0) { + LOG_W(NR_RRC, + "SIB3: no intra-frequency neighbours found for serving SSB ARFCN %u " + "(configured neighbours %u), not building SIB3\n", + serving_ssb_arfcn, + (unsigned)seq_arr_size(neighbours)); + asn1cFreeStruc(asn_DEF_NR_SIB3, sib3); + return NULL; + } + + return sib3; +} + /** @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) */ @@ -569,6 +702,29 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) free_byte_array(enc); LOG_I(NR_RRC, "DU %ld: added SIB2 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); } break; + case NR_SIB_3: { + if (!rrc->neighbour_cell_configuration) + break; + const neighbour_cell_configuration_t *neighbour_config = get_cell_neighbour_list(rrc, new); + if (!neighbour_config) + break; + const seq_arr_t *neigh_cells = &neighbour_config->neighbour_cells; + + NR_SIB3_t *sib3 = get_sib3_intra_freq_neighbors(neigh_cells, get_ssb_arfcn(new)); + if (!sib3) + break; + + byte_array_t enc = do_SIB3_NR(sib3); + ASN_STRUCT_FREE(asn_DEF_NR_SIB3, sib3); + if (!enc.buf || enc.len == 0) { + free_byte_array(enc); + LOG_E(NR_RRC, "SIB3 encoding failed\n"); + break; + } + add_si_msg(&cell, sib->SIB_type, &enc); + free_byte_array(enc); + LOG_I(NR_RRC, "DU %ld: added SIB3 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); + } break; default: AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); } From 6f9a716eb9eb854d9ac693b00dae38dbd8a3f6bd Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 11 Feb 2026 10:56:24 +0100 Subject: [PATCH 13/18] RRC/MAC: add SIB4 inter-frequency neighbour support Add SIB4 encode/decode helpers and populate SIB4 from configured inter-frequency carriers and neighbour cells, wiring it into DU F1 Setup handling and MAC SystemInformation. Changes: - Add do_SIB4_NR() and NR_SIB4.h inclusion in asn1_msg.c/asn1_msg.h to UPER-encode NR_SIB4_t into a byte_array_t. - Extend nr_mac_configure_other_sib() in NR_MAC_gNB/config.c with an NR_SIB_4 case that validates the SIB container, decodes NR_SIB4_t via uper_decode, logs failures, and appends SIB4 to SystemInformation. - Introduce build_inter_freq_carrier_from_cfg() and get_sib4_inter_freq_neighbors() in rrc_gNB_du.c to build NR_InterFreqCarrierFreqInfo_t and NR_SIB4_t from inter-frequency and neighbour cell configuration, skipping invalid or empty carriers. - Handle NR_SIB_4 in rrc_gNB_process_f1_setup_req() by deriving SIB4 from neighbour/inter-frequency configuration, encoding it with do_SIB4_NR(), validating the resulting byte_array_t and adding the SI message for the DU cell when encoding succeeds. --- openair2/LAYER2/NR_MAC_gNB/config.c | 15 +++ openair2/RRC/NR/MESSAGES/asn1_msg.c | 21 ++++ openair2/RRC/NR/MESSAGES/asn1_msg.h | 2 + openair2/RRC/NR/rrc_gNB_du.c | 149 ++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index f38eebd326..1d217b7c72 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1023,6 +1023,21 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ add_sib_to_systeminformation(sysInfo, type); break; } + case NR_SIB_4: { + struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc_or_fail(1, sizeof(*type)); + type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib4; + NR_SIB4_t *sib4 = NULL; + asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB4, (void **)&sib4, container->buf, container->len, 0, 0); + if (dec_rval.code != RC_OK) { + LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + free(type); + break; + } + type->choice.sib4 = sib4; + add_sib_to_systeminformation(sysInfo, type); + break; + } default : AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]); } diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.c b/openair2/RRC/NR/MESSAGES/asn1_msg.c index 80235ebdb7..d818229054 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.c +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.c @@ -265,6 +265,27 @@ byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3) return msg; } +byte_array_t do_SIB4_NR(NR_SIB4_t *sib4) +{ + byte_array_t msg = {.buf = NULL, .len = 0}; + char errbuf[256] = {0}; + size_t errlen = sizeof(errbuf); + int ret = asn_check_constraints(&asn_DEF_NR_SIB4, sib4, errbuf, &errlen); + if (ret != 0) { + LOG_E(NR_RRC, "SIB4 constraint check failed: %s\n", errbuf); + return msg; + } + + int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB4, NULL, (void *)sib4, (void **)&msg.buf); + if (val <= 0) { + LOG_E(NR_RRC, "Failed to encode SIB4\n"); + return msg; + } + + msg.len = val; + return msg; +} + int do_RRCReject(uint8_t *const buffer) { asn_enc_rval_t enc_rval; diff --git a/openair2/RRC/NR/MESSAGES/asn1_msg.h b/openair2/RRC/NR/MESSAGES/asn1_msg.h index 11963780f1..384096ee16 100644 --- a/openair2/RRC/NR/MESSAGES/asn1_msg.h +++ b/openair2/RRC/NR/MESSAGES/asn1_msg.h @@ -35,6 +35,7 @@ #include "NR_UE-CapabilityRAT-ContainerList.h" #include "NR_SIB2.h" #include "NR_SIB3.h" +#include "NR_SIB4.h" #include "ds/seq_arr.h" #include "ds/byte_array.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" @@ -69,6 +70,7 @@ int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2); byte_array_t do_SIB3_NR(const NR_SIB3_t *sib3); +byte_array_t do_SIB4_NR(NR_SIB4_t *sib4); int do_RRCReject(uint8_t *const buffer); diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 43692ec742..9843d02b83 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -25,6 +25,7 @@ #include "nr_rrc_defs.h" #include "NR_SIB2.h" #include "NR_SIB3.h" +#include "NR_SIB4.h" #include "NR_Q-OffsetRange.h" #include "openair2/RRC/NR/MESSAGES/asn1_msg.h" #include "rrc_gNB_mobility.h" @@ -305,6 +306,130 @@ static NR_SIB3_t *get_sib3_intra_freq_neighbors(const seq_arr_t *neighbours, uin return sib3; } +/** @brief Build an NR_InterFreqCarrierFreqInfo structure from a nr_inter_freq_cfg_t configuration. + * @param cfg Configuration to build the carrier from + * @return Allocated NR_InterFreqCarrierFreqInfo_t on success, NULL if the configuration is invalid */ +static NR_InterFreqCarrierFreqInfo_t *build_inter_freq_carrier_from_cfg(const nr_inter_freq_cfg_t *cfg) +{ + DevAssert(cfg); + + const nr_neighbour_cell_sib4_freq_t *f = &cfg->freq_cfg; + + NR_InterFreqCarrierFreqInfo_t *carrier = calloc_or_fail(1, sizeof(*carrier)); + /* dl_CarrierFreq (ARFCN) */ + carrier->dl_CarrierFreq = cfg->arfcn; + /* ssbSubcarrierSpacing: (15/30/60/120 kHz) */ + carrier->ssbSubcarrierSpacing = cfg->scs; + carrier->deriveSSB_IndexFromCell = true; + /* Q-RxLevMin (-70..-22 dBm) */ + carrier->q_RxLevMin = cfg->q_RxLevMin; + /* T-Reselection (0..7) */ + carrier->t_ReselectionNR = cfg->t_ReselectionNR; + /* threshX_HighP/LowP: Reselection threshold (0..31) */ + carrier->threshX_HighP = f->threshX_HighP; + carrier->threshX_LowP = f->threshX_LowP; + /* threshX-Q: Optional RSRQ thresholds; emit only when both are provided. */ + if (f->threshX_HighQ != -1 && f->threshX_LowQ != -1) { + carrier->threshX_Q = calloc_or_fail(1, sizeof(*carrier->threshX_Q)); + carrier->threshX_Q->threshX_HighQ = f->threshX_HighQ; + carrier->threshX_Q->threshX_LowQ = f->threshX_LowQ; + } else if (f->threshX_HighQ != -1 || f->threshX_LowQ != -1) { + LOG_W(NR_RRC, + "SIB4: partial threshX-Q for ARFCN %d (HighQ=%d LowQ=%d), omitting threshX-Q\n", + cfg->arfcn, + f->threshX_HighQ, + f->threshX_LowQ); + } + /* CellReselectionPriority (0..7) */ + if (f->cellReselectionPriority != -1) + asn1cCallocOne(carrier->cellReselectionPriority, f->cellReselectionPriority); + /* q-OffsetFreq (-24..24 dB), 0 dB default */ + asn1cCallocOne(carrier->q_OffsetFreq, get_q_offset_asn1(f->q_OffsetFreq)); + /* InterFreqNeighCellList */ + carrier->interFreqNeighCellList = calloc_or_fail(1, sizeof(*carrier->interFreqNeighCellList)); + return carrier; +} + +/** + * @brief Build an NR_SIB4 structure populated with inter-frequency neighbour cell entries. + * + * Iterates over all configured inter-frequency carriers (excluding the serving cell ARFCN) + * and, for each carrier, adds the matching neighbour cells from @p neighbour_config. + * Carriers with no matching neighbours are discarded. If no valid carrier is found, + * the function returns NULL. + * + * @param neighbour_config Neighbour cell configuration (cells only). + * @param inter_freqs gNB-level inter-frequency configuration. + * @param serving_ssb_arfcn SSB ARFCN of the serving cell, used to skip the serving carrier. + * @return Allocated NR_SIB4_t on success, NULL if no inter-frequency neighbours are available. + */ +static NR_SIB4_t *get_sib4_inter_freq_neighbors(const neighbour_cell_configuration_t *neighbour_config, + const seq_arr_t *inter_freqs, + uint32_t serving_ssb_arfcn) +{ + DevAssert(neighbour_config); + + NR_SIB4_t *sib4 = calloc_or_fail(1, sizeof(*sib4)); + + const seq_arr_t *neigh = &neighbour_config->neighbour_cells; + + FOR_EACH_SEQ_ARR (nr_inter_freq_cfg_t *, cfg, inter_freqs) { + if (cfg->arfcn == serving_ssb_arfcn) + continue; + + NR_InterFreqCarrierFreqInfo_t *carrier = build_inter_freq_carrier_from_cfg(cfg); + if (!carrier) + continue; + + NR_InterFreqNeighCellList_t *neigh_list = carrier->interFreqNeighCellList; + + FOR_EACH_SEQ_ARR (nr_neighbour_cell_t *, nc, neigh) { + if (nc->absoluteFrequencySSB != cfg->arfcn || nc->subcarrierSpacing != cfg->scs) + continue; + + if (neigh_list->list.count >= NR_maxCellInter) { + LOG_W(NR_RRC, + "SIB4: too many inter-frequency neighbors for ARFCN %ld (max %d), skipping neighbour\n", + carrier->dl_CarrierFreq, + NR_maxCellInter); + continue; + } + + const nr_neighbour_cell_neighbor_offset_t *offset = &nc->sib4.offset; + LOG_I(NR_RRC, + "SIB4: added inter-frequency neighbour cell: cellId %ld (PCI %d), ARFCN %d, SCS %d\n", + nc->nrcell_id, + nc->physicalCellId, + cfg->arfcn, + cfg->scs); + asn1cSequenceAdd(neigh_list->list, NR_InterFreqNeighCellInfo_t, cell); + cell->physCellId = nc->physicalCellId; + + /* q_OffsetCell: 0 dB is default */ + cell->q_OffsetCell = get_q_offset_asn1(offset->q_OffsetCell); + + /* q_RxLevMinOffsetCell / q_QualMinOffsetCell: range 1..8 */ + if (offset->q_RxLevMinOffsetCell != -1) + asn1cCallocOne(cell->q_RxLevMinOffsetCell, offset->q_RxLevMinOffsetCell); + if (offset->q_QualMinOffsetCell != -1) + asn1cCallocOne(cell->q_QualMinOffsetCell, offset->q_QualMinOffsetCell); + } + + if (neigh_list->list.count > 0) { + asn1cSeqAdd(&sib4->interFreqCarrierFreqList.list, carrier); + } else { + ASN_STRUCT_FREE(asn_DEF_NR_InterFreqCarrierFreqInfo, carrier); + } + } + + if (sib4->interFreqCarrierFreqList.list.count == 0) { + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + return NULL; + } + + return sib4; +} + /** @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) */ @@ -725,6 +850,30 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) free_byte_array(enc); LOG_I(NR_RRC, "DU %ld: added SIB3 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); } break; + case NR_SIB_4: { + if (!rrc->neighbour_cell_configuration) + break; + const neighbour_cell_configuration_t *neighbour_cfg = get_cell_neighbour_list(rrc, new); + if (!neighbour_cfg) + break; + + NR_SIB4_t *sib4 = get_sib4_inter_freq_neighbors(neighbour_cfg, &rrc->inter_freqs, get_ssb_arfcn(new)); + if (!sib4) { + LOG_W(NR_RRC, "SIB4: could not build from neighbours, skipping\n"); + break; + } + + byte_array_t enc = do_SIB4_NR(sib4); + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + if (!enc.buf || enc.len == 0) { + free_byte_array(enc); + LOG_E(NR_RRC, "SIB4 encoding failed\n"); + break; + } + add_si_msg(&cell, sib->SIB_type, &enc); + free_byte_array(enc); + LOG_I(NR_RRC, "DU %ld: added SIB4 to F1 Setup Response (cell %ld)\n", du->gNB_DU_id, new->info.cell_id); + } break; default: AssertFatal(false, "SIB%d not handled yet\n", sib->SIB_type); } From 4569ef199d2cee6c803530f61b6afefbb3905e0e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 15 Jan 2026 23:49:40 +0100 Subject: [PATCH 14/18] Refactor (RRC): drop isIntraFrequencyNeighbour flag and labelling Remove the isIntraFrequencyNeighbour field and all code that sets or uses it, simplifying neighbour cell handling and avoiding redundant state in the RRC gNB code. The flag duplicated information that can be derived from the neighbour absoluteFrequencySSB and the serving cell SSB ARFCN, and intra-frequency neighbours are logged in the SIB3 preparation flow. Changes: - Remove isIntraFrequencyNeighbour from nr_neighbour_cell_t in openair2/RRC/NR/nr_rrc_defs.h - Delete is_intra_frequency_neighbour() and label_intra_frequency_neighbours() helpers from openair2/RRC/NR/rrc_gNB_du.c - Remove calls to label_intra_frequency_neighbours() from rrc_gNB_process_f1_setup_req() and rrc_gNB_process_f1_du_configuration_update() in rrc_gNB_du.c - Stop setting .isIntraFrequencyNeighbour in the local nr_neighbour_cell_t neighbourConfig initializer in openair2/RRC/NR/rrc_gNB_mobility.c --- openair2/RRC/NR/nr_rrc_defs.h | 1 - openair2/RRC/NR/rrc_gNB_du.c | 42 ------------------------------ openair2/RRC/NR/rrc_gNB_mobility.c | 1 - 3 files changed, 44 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index f0e212c79f..113fbe2f94 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -387,7 +387,6 @@ typedef struct { int band; plmn_id_t plmn; uint32_t tac; - bool isIntraFrequencyNeighbour; // SIB3 (intra-frequency neighbor cell-specific offsets) nr_neighbour_cell_sib3_t sib3; // SIB4 (inter-frequency neighbor cell-specific parameters) diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 9843d02b83..6b60e5b6b9 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -538,41 +538,6 @@ static const neighbour_cell_configuration_t *get_cell_neighbour_list(const gNB_R return (const neighbour_cell_configuration_t *)it; } -static void is_intra_frequency_neighbour(void *ssb_arfcn, void *neighbour_cell) -{ - uint32_t *ssb_arfcn_ptr = (uint32_t *)ssb_arfcn; - nr_neighbour_cell_t *neighbour_cell_ptr = (nr_neighbour_cell_t *)neighbour_cell; - - if (*ssb_arfcn_ptr == neighbour_cell_ptr->absoluteFrequencySSB) { - LOG_D(NR_RRC, "HO LOG: found intra frequency neighbour %lu!\n", neighbour_cell_ptr->nrcell_id); - neighbour_cell_ptr->isIntraFrequencyNeighbour = true; - } -} -/** - * @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 Pointer to cell container - */ -static void label_intra_frequency_neighbours(gNB_RRC_INST *rrc, const nr_rrc_cell_container_t *cell) -{ - if (!rrc->neighbour_cell_configuration) - return; - - const neighbour_cell_configuration_t *neighbour_cell_config = get_cell_neighbour_list(rrc, cell); - if (!neighbour_cell_config) - return; - - 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); - - const seq_arr_t *cell_neighbour_list = &neighbour_cell_config->neighbour_cells; - for_each((seq_arr_t *)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) { // MTC is mandatory, but some DUs don't send it in the F1 Setup Request, so @@ -880,9 +845,6 @@ void rrc_gNB_process_f1_setup_req(f1ap_setup_req_t *req, sctp_assoc_t assoc_id) } } - if (new->mib != NULL &&new->sib1 != NULL) - label_intra_frequency_neighbours(rrc, new); - resp.cells_to_activate[i] = cell; } @@ -1098,10 +1060,6 @@ void rrc_gNB_process_f1_du_configuration_update(f1ap_gnb_du_configuration_update LOG_I(NR_RRC, "update system information of DU %ld\n", du->gNB_DU_id); } } - - if (cell && cell->mib != NULL && cell->sib1 != NULL && cell->assoc_id == du->assoc_id) { - label_intra_frequency_neighbours(rrc, cell); - } } if (conf_up->num_cells_to_delete > 0) { diff --git a/openair2/RRC/NR/rrc_gNB_mobility.c b/openair2/RRC/NR/rrc_gNB_mobility.c index e36e6ee684..fdbc2d66af 100644 --- a/openair2/RRC/NR/rrc_gNB_mobility.c +++ b/openair2/RRC/NR/rrc_gNB_mobility.c @@ -607,7 +607,6 @@ void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t 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->gNB_DU_id, .nrcell_id = pcell->info.cell_id, .physicalCellId = pcell->info.pci, From da7054092a1a10985fa540895d06e2a520e43933 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 15 Jan 2026 23:44:26 +0100 Subject: [PATCH 15/18] Reorganize and document nr_neighbour_cell_t structure - Add comprehensive documentation header explaining the structure's role as single source of truth for neighbor cell information across multiple protocols (NGAP/XnAP handover, measurement config, DU validation, SIB3/SIB4) - Add detailed field comments documenting protocol-specific mappings: * Core identification fields (gNB_ID, nrcell_id, physicalCellId) * Frequency/RF parameters (absoluteFrequencySSB, subcarrierSpacing, band) * Handover target identification (plmn, tac) - Document NGAP/XnAP IE mappings for plmn and tac fields --- openair2/RRC/NR/nr_rrc_defs.h | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 113fbe2f94..5a4063c3f2 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -378,14 +378,31 @@ typedef struct { nr_neighbour_cell_sib4_freq_t freq_cfg; } nr_inter_freq_cfg_t; +/** @brief Neighbor cell configuration structure + * Single source of truth for neighbor cell information, used across multiple protocols/scopes: + * - Handover (NGAP/XnAP): for target gNB (ID, plmn, tac, nrcell_id, physicalCellId) + * - Measurement (MeasConfig): physicalCellId, absoluteFrequencySSB, band + * - DU validation: all fields validated against actual cell information + * - SIB3/SIB4 generation: physicalCellId, absoluteFrequencySSB, subcarrierSpacing, band + * References: + * - 3GPP TS 38.331 (RRC) for SIB3/SIB4 and MeasConfig + * - 3GPP TS 38.413 (NGAP) / TS 38.423 (XnAP) for target cell identification */ typedef struct { + // gNB identifier (for target node in NGAP/XnAP handover) uint32_t gNB_ID; + // NR Cell Global Identifier (for NGAP/XnAP handover, DU validation) uint64_t nrcell_id; + // Physical Cell ID (PCI) (used in HandoverPreparationInformation and MeasObjectNR) int physicalCellId; + // SSB absolute frequency (ARFCN) (for MeasObjectNR, intra/inter-frequency determination) int absoluteFrequencySSB; + // SSB subcarrier spacing (for MeasObjectNR/SIB4) int subcarrierSpacing; + // Frequency band indicator (for MeasObjectNR/SIB4) int band; + // PLMN identity (for target node in NGAP/XnAP handover) plmn_id_t plmn; + // Tracking Area Code (for target node in NGAP/XnAP handover) uint32_t tac; // SIB3 (intra-frequency neighbor cell-specific offsets) nr_neighbour_cell_sib3_t sib3; From 3e515d9c9c2de1b5e0aef115944e92779b1fd6f2 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 3 Mar 2026 13:19:40 +0100 Subject: [PATCH 16/18] Tests: add SIB2/SIB3/SIB4 ASN.1 round-trip coverage Add encode/decode helpers and basic round-trip tests for NR SIB2, SIB3 and SIB4 in test_asn1_msg.cpp to ensure we produce decodable messages with expected fields. Extend SIB4 coverage with an inter_freq_carrier_freq_info_ranges test that exercises boundary and out-of-range values for carrier reselection parameters and checks encoder failures for invalid configurations. Changes: - Introduce encode_and_decode_sib2, encode_and_decode_sib3 and encode_and_decode_sib4 helpers that call do_SIB2_NR, do_SIB3_NR, do_SIB4_NR and uper_decode and assert non-empty, non-null byte arrays and successful decoding. - Add sib2_basic_encode_decode, sib3_basic_encode_decode and sib4_basic_encode_decode tests that populate minimal but representative NR_SIB2_t, NR_SIB3_t and NR_SIB4_t instances and verify selected decoded fields match the originals. - Add inter_freq_carrier_freq_info_ranges test that builds an NR_SIB4_t with NR_InterFreqCarrierFreqInfo_t and varies different offsets and thresholds to confirm valid combinations encode and invalid ones are rejected. --- .../RRC/NR/MESSAGES/tests/test_asn1_msg.cpp | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) diff --git a/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp b/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp index 4867d7f8ab..c14e9bdba0 100644 --- a/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp +++ b/openair2/RRC/NR/MESSAGES/tests/test_asn1_msg.cpp @@ -14,12 +14,61 @@ extern "C" { #include "NR_DRB-ToAddMod.h" #include "NR_DRB-ToAddModList.h" #include "NR_SRB-ToAddModList.h" +#include "NR_InterFreqCarrierFreqInfo.h" #include "ds/byte_array.h" +#include "NR_SIB2.h" +#include "NR_SIB3.h" +#include "NR_SIB4.h" +#include "NR_IntraFreqNeighCellInfo.h" +#include "NR_IntraFreqNeighCellList.h" +#include "NR_InterFreqCarrierFreqInfo.h" +#include "NR_InterFreqNeighCellInfo.h" +#include "NR_InterFreqNeighCellList.h" +#include "asn_application.h" RAN_CONTEXT_t RC; #ifdef __cplusplus } #endif +static NR_SIB2_t *encode_and_decode_sib2(const NR_SIB2_t *sib2, byte_array_t *ba_out) +{ + *ba_out = do_SIB2_NR(sib2); + EXPECT_GT(ba_out->len, 0); + EXPECT_NE(ba_out->buf, nullptr); + + NR_SIB2_t *decoded = nullptr; + asn_dec_rval_t dec = uper_decode(nullptr, &asn_DEF_NR_SIB2, (void **)&decoded, ba_out->buf, ba_out->len, 0, 0); + EXPECT_EQ(dec.code, RC_OK); + EXPECT_NE(decoded, nullptr); + return decoded; +} + +static NR_SIB3_t *encode_and_decode_sib3(const NR_SIB3_t *sib3, byte_array_t *ba_out) +{ + *ba_out = do_SIB3_NR(sib3); + EXPECT_GT(ba_out->len, 0); + EXPECT_NE(ba_out->buf, nullptr); + + NR_SIB3_t *decoded = nullptr; + asn_dec_rval_t dec = uper_decode(nullptr, &asn_DEF_NR_SIB3, (void **)&decoded, ba_out->buf, ba_out->len, 0, 0); + EXPECT_EQ(dec.code, RC_OK); + EXPECT_NE(decoded, nullptr); + return decoded; +} + +static NR_SIB4_t *encode_and_decode_sib4(NR_SIB4_t *sib4, byte_array_t *ba_out) +{ + *ba_out = do_SIB4_NR(sib4); + EXPECT_GT(ba_out->len, 0); + EXPECT_NE(ba_out->buf, nullptr); + + NR_SIB4_t *decoded = nullptr; + asn_dec_rval_t dec = uper_decode(nullptr, &asn_DEF_NR_SIB4, (void **)&decoded, ba_out->buf, ba_out->len, 0, 0); + EXPECT_EQ(dec.code, RC_OK); + EXPECT_NE(decoded, nullptr); + return decoded; +} + TEST(nr_asn1, rrc_reject) { unsigned char buf[1000]; @@ -138,6 +187,167 @@ TEST(nr_asn1, rrc_reconfiguration) free_RRCReconfiguration_params(params); } +TEST(nr_asn1, sib2_basic_encode_decode) +{ + NR_SIB2_t *sib2 = (NR_SIB2_t *)calloc_or_fail(1, sizeof(*sib2)); + sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; + sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 3; + sib2->cellReselectionServingFreqInfo.threshServingLowP = 10; + sib2->intraFreqCellReselectionInfo.q_RxLevMin = -56; + sib2->intraFreqCellReselectionInfo.s_IntraSearchP = 22; + sib2->intraFreqCellReselectionInfo.t_ReselectionNR = 1; + sib2->intraFreqCellReselectionInfo.deriveSSB_IndexFromCell = true; + + byte_array_t ba = {}; + NR_SIB2_t *decoded = encode_and_decode_sib2(sib2, &ba); + + EXPECT_EQ(decoded->cellReselectionServingFreqInfo.cellReselectionPriority, + sib2->cellReselectionServingFreqInfo.cellReselectionPriority); + EXPECT_EQ(decoded->cellReselectionServingFreqInfo.threshServingLowP, sib2->cellReselectionServingFreqInfo.threshServingLowP); + + ASN_STRUCT_FREE(asn_DEF_NR_SIB2, sib2); + ASN_STRUCT_FREE(asn_DEF_NR_SIB2, decoded); + free_byte_array(ba); +} + +TEST(nr_asn1, sib3_basic_encode_decode) +{ + NR_SIB3_t *sib3 = (NR_SIB3_t *)calloc_or_fail(1, sizeof(*sib3)); + sib3->intraFreqNeighCellList = (struct NR_IntraFreqNeighCellList *)calloc_or_fail(1, sizeof(*sib3->intraFreqNeighCellList)); + + NR_IntraFreqNeighCellInfo_t *cell = (NR_IntraFreqNeighCellInfo_t *)calloc_or_fail(1, sizeof(*cell)); + cell->physCellId = 100; + cell->q_OffsetCell = NR_Q_OffsetRange_dB0; + ASN_SEQUENCE_ADD(&sib3->intraFreqNeighCellList->list, cell); + + byte_array_t ba = {}; + NR_SIB3_t *decoded = encode_and_decode_sib3(sib3, &ba); + + ASSERT_NE(decoded->intraFreqNeighCellList, nullptr); + EXPECT_EQ(decoded->intraFreqNeighCellList->list.count, 1); + EXPECT_EQ(decoded->intraFreqNeighCellList->list.array[0]->physCellId, cell->physCellId); + + ASN_STRUCT_FREE(asn_DEF_NR_SIB3, sib3); + ASN_STRUCT_FREE(asn_DEF_NR_SIB3, decoded); + free_byte_array(ba); +} + +TEST(nr_asn1, sib4_basic_encode_decode) +{ + NR_SIB4_t *sib4 = (NR_SIB4_t *)calloc_or_fail(1, sizeof(*sib4)); + + NR_InterFreqCarrierFreqInfo_t *carrier = (NR_InterFreqCarrierFreqInfo_t *)calloc_or_fail(1, sizeof(*carrier)); + carrier->dl_CarrierFreq = 2000; + carrier->ssbSubcarrierSpacing = NR_SubcarrierSpacing_kHz15; + carrier->deriveSSB_IndexFromCell = true; + carrier->q_RxLevMin = -56; + carrier->t_ReselectionNR = 1; + carrier->threshX_HighP = 5; + carrier->threshX_LowP = 3; + carrier->interFreqNeighCellList = (struct NR_InterFreqNeighCellList *)calloc_or_fail(1, sizeof(*carrier->interFreqNeighCellList)); + + NR_InterFreqNeighCellInfo_t *ncell = (NR_InterFreqNeighCellInfo_t *)calloc_or_fail(1, sizeof(*ncell)); + ncell->physCellId = 200; + ncell->q_OffsetCell = NR_Q_OffsetRange_dB0; + ASN_SEQUENCE_ADD(&carrier->interFreqNeighCellList->list, ncell); + ASN_SEQUENCE_ADD(&sib4->interFreqCarrierFreqList.list, carrier); + + byte_array_t ba = {}; + NR_SIB4_t *decoded = encode_and_decode_sib4(sib4, &ba); + + ASSERT_GT(decoded->interFreqCarrierFreqList.list.count, 0); + EXPECT_EQ(decoded->interFreqCarrierFreqList.list.array[0]->dl_CarrierFreq, carrier->dl_CarrierFreq); + + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, decoded); + free_byte_array(ba); +} + +TEST(nr_asn1, inter_freq_carrier_freq_info_ranges) +{ + NR_SIB4_t *sib4 = (NR_SIB4_t *)calloc_or_fail(1, sizeof(*sib4)); + ASN_SEQUENCE_ADD(&sib4->interFreqCarrierFreqList.list, + (NR_InterFreqCarrierFreqInfo_t *)calloc_or_fail(1, sizeof(NR_InterFreqCarrierFreqInfo_t))); + + NR_InterFreqCarrierFreqInfo_t *carrier = sib4->interFreqCarrierFreqList.list.array[0]; + carrier->dl_CarrierFreq = 640000; + carrier->ssbSubcarrierSpacing = NR_SubcarrierSpacing_kHz30; + carrier->deriveSSB_IndexFromCell = 1; + carrier->threshX_HighP = 0; + carrier->threshX_LowP = 0; + + NR_Q_OffsetRange_t *qoff = (NR_Q_OffsetRange_t *)calloc_or_fail(1, sizeof(*qoff)); + *qoff = 15; // arbitrary valid Q-OffsetRange enum + carrier->q_OffsetFreq = qoff; + + // Optional threshX_Q struct + carrier->threshX_Q = (decltype(carrier->threshX_Q))calloc_or_fail(1, sizeof(*carrier->threshX_Q)); + carrier->threshX_Q->threshX_HighQ = 0; + carrier->threshX_Q->threshX_LowQ = 0; + + auto try_encode = [](NR_SIB4_t *s, bool expect_success) { + byte_array_t ba = do_SIB4_NR(s); + int len = (int)ba.len; + if (expect_success) { + EXPECT_GT(len, 0); + EXPECT_NE(ba.buf, nullptr); + } else { + EXPECT_LE(len, 0); + } + free_byte_array(ba); + }; + + // Valid lower bounds for all constrained scalars + carrier->q_RxLevMin = -70; + carrier->t_ReselectionNR = 0; + carrier->threshX_HighP = 0; + carrier->threshX_LowP = 0; + carrier->threshX_Q->threshX_HighQ = 0; + carrier->threshX_Q->threshX_LowQ = 0; + *qoff = 15; + try_encode(sib4, true); + + // Invalid q_RxLevMin (below ASN.1 range) + carrier->q_RxLevMin = -80; + carrier->t_ReselectionNR = 0; + try_encode(sib4, false); + + // Invalid t_ReselectionNR (above ASN.1 range) + carrier->q_RxLevMin = -70; + carrier->t_ReselectionNR = 8; + try_encode(sib4, false); + + // Restore valid q_RxLevMin / t_ReselectionNR + carrier->q_RxLevMin = -70; + carrier->t_ReselectionNR = 0; + + // Invalid threshX_HighP (below 0) + carrier->threshX_HighP = -1; + try_encode(sib4, false); + carrier->threshX_HighP = 0; + + // Invalid threshX_LowP (below 0) + carrier->threshX_LowP = -1; + try_encode(sib4, false); + carrier->threshX_LowP = 0; + + // Invalid threshX_HighQ (below 0) + carrier->threshX_Q->threshX_HighQ = -1; + try_encode(sib4, false); + carrier->threshX_Q->threshX_HighQ = 0; + + // Invalid threshX_LowQ (below 0) + carrier->threshX_Q->threshX_LowQ = -1; + try_encode(sib4, false); + carrier->threshX_Q->threshX_LowQ = 0; + + // Invalid q_OffsetFreq (outside [-24,24] enum range) + *qoff = 99; + try_encode(sib4, false); + + ASN_STRUCT_FREE(asn_DEF_NR_SIB4, sib4); +} + int main(int argc, char **argv) { logInit(); From c866078b2168f7e7c248b3c620d8a602ae4ae530 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 7 Apr 2026 14:19:09 +0200 Subject: [PATCH 17/18] Tests: enable SIB2/SIB3/SIB4 example config in F1 rfsim CI Update the shared neighbour config used by the F1 rfsim pipeline to enable CU-provided SIB2 and neighbour-derived SIB3/SIB4 inputs. In `ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf` - Add `cu_sibs = (2, 3, 4) In `ci-scripts/conf_files/neighbour-config.conf`: - Add a complete `sib2_config` block - Add `frequency_list` / `frequency_config` for inter-frequency SIB4 fields - Extend both neighbour entries with per-cell offsets --- .../conf_files/gnb-cu.sa.band78.106prb.conf | 1 + ci-scripts/conf_files/neighbour-config.conf | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf index 015d77cacc..8460983d2b 100644 --- a/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf +++ b/ci-scripts/conf_files/gnb-cu.sa.band78.106prb.conf @@ -17,6 +17,7 @@ gNBs = // Tracking area code, 0x0000 and 0xfffe are reserved values tracking_area_code = 1; plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) }); + cu_sibs = ( 2, 3, 4 ); @include "neighbour-config.conf" nr_cellid = 12345678L; diff --git a/ci-scripts/conf_files/neighbour-config.conf b/ci-scripts/conf_files/neighbour-config.conf index fe91ff2f08..57aa55f772 100644 --- a/ci-scripts/conf_files/neighbour-config.conf +++ b/ci-scripts/conf_files/neighbour-config.conf @@ -5,6 +5,57 @@ # for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) # ############################################################ +############################################################ +# SIB2 # +############################################################ + +sib2_config : { + q_Hyst = 0; + cellReselectionPriority = 0; + threshServingLowP = 0; + threshServingLowQ = 0; + s_NonIntraSearchP = -1; + s_NonIntraSearchQ = -1; + q_RxLevMin = -56; + q_QualMin = -1; + s_IntraSearchP = 22; + s_IntraSearchQ = -1; + t_ReselectionNR = 1; + deriveSSB_IndexFromCell = 1; + + speed_t_Evaluation = 0; + speed_t_HystNormal = 0; + speed_n_CellChangeMedium = 1; + speed_n_CellChangeHigh = 2; + speed_sf_Medium = 1; + speed_sf_High = 0; +}; + +############################################################ +# Per-frequency SIB4 inter-frequency configuration # +############################################################ + +frequency_list = ( + { + absoluteFrequencySSB = 621312; + subcarrierSpacing = 1; # 30 kHz + band = 78; + + frequency_config = ( + { + cellReselectionPriority = 5; + threshX_HighP = 10; + threshX_LowP = 6; + threshX_HighQ = -1; + threshX_LowQ = -1; + q_OffsetFreq = 0; + q_RxLevMin = -56; + t_ReselectionNR = 1; + } + ); + } +); + neighbour_list = ( ########################################################## # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) # @@ -21,6 +72,9 @@ neighbour_list = ( band = 78; plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; tracking_area_code = 1; + q_OffsetCell = 2; + q_RxLevMinOffsetCell= 2; + q_QualMinOffsetCell = 1; } ); }, @@ -40,6 +94,9 @@ neighbour_list = ( band = 78; plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; tracking_area_code = 1; + q_OffsetCell = 2; + q_RxLevMinOffsetCell= 2; + q_QualMinOffsetCell = 1; } ); } From 22783bb3a75173862d0b0c8d4048c16e3910f12f Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 3 Mar 2026 14:54:08 +0100 Subject: [PATCH 18/18] doc (RRC): document neighbour configuration, SIB3/4 and MeasGaps design Expand RRC usage and handover documentation to describe the two-level neighbour configuration layout, lookup-key semantics, and config-time SIB4 grouping behaviour. Expand the example config to include a `frequency_list` block and per-neighbour SIB3/SIB4 offset fields. Add SIB2 config example. Update handover-tutorial.md to describe the same nested model and note that F1 and N2 handover share the same serving-cell keyed mapping. Add documentation about SIB3/SIB4 and MeasGaps implementation in OAI with stress on the shared neighbour configuration data model. Update FEATURE_SET. --- doc/FEATURE_SET.md | 6 +- doc/RRC/rrc-dev.md | 92 ++++++++++++++++++++++ doc/RRC/rrc-usage.md | 164 +++++++++++++++++++++++++++++++++++++-- doc/handover-tutorial.md | 12 ++- 4 files changed, 263 insertions(+), 11 deletions(-) diff --git a/doc/FEATURE_SET.md b/doc/FEATURE_SET.md index 95aa32166f..bc6ba6b4a3 100644 --- a/doc/FEATURE_SET.md +++ b/doc/FEATURE_SET.md @@ -154,7 +154,7 @@ These modes of operation are supported: - RedCap SIB1 v17 IEs supported - Coexistence of RedCap and Normal UEs - Handling of RedCap capability for small PDCP/RLC SN size -- Scheduling of other SIBs (2, 19) +- Scheduling of other SIBs (2, 3, 4, 19) - NTN - Support downlinkHARQ-FeedbackDisabled-r17 - Support for 32 PDSCH and PUSCH HARQ processes per UE @@ -194,7 +194,7 @@ These modes of operation are supported: - NR RRC (38.331) Rel 17 messages using new [asn1c](https://github.com/mouse07410/asn1c) - LTE RRC (36.331) also updated to Rel 15 -- Generation of system information (SIB2) +- Generation of system information (SIB2, SIB3, SIB4) - RRC can configure PDCP and SDAP (through E1), and RLC and MAC (through F1) - Interface with GTP-U (tunnel creation/handling for S1-U (NSA), N3 (SA), F1 interfaces) - Integration of RRC messages and procedures supporting UE 5G SA connection @@ -380,7 +380,7 @@ These modes of operation are supported: - MIB processing - Scheduling of system information block 1 (SIB1) reception * Other system information - - Scheduling of other system information blocks reception + - Scheduling of other system information blocks reception (SIB2, SIB3, SIB4) * Random access procedure (needs improvement, there is still not a clear separation between MAC and PHY) - Mapping SSBs to multiple ROs - Scheduling of PRACH diff --git a/doc/RRC/rrc-dev.md b/doc/RRC/rrc-dev.md index eee1b861fa..a218997d3a 100644 --- a/doc/RRC/rrc-dev.md +++ b/doc/RRC/rrc-dev.md @@ -633,6 +633,98 @@ sequenceDiagram Note over TargetCU: Then: encode Handover Command, send NGAP HANDOVER REQUEST ACKNOWLEDGE ``` +### Neighbour cells + +#### SIB3/SIB4 and measurement-gap implementation + +The following section documents the implementation-level control flow +for SIB3/SIB4 and measurement-gap handling in current OAI, using the configured +neighbor cell list as a shared input model. + +Briefly, the three procedures are: +- SIB3: CU derives and provides intra-frequency neighbour SI, which DU broadcasts + and UE uses for autonomous idle/inactive intra-frequency reselection. +- SIB4: CU derives and provides inter-frequency carrier/neighbour SI, which DU + broadcasts and UE uses for autonomous idle/inactive inter-frequency reselection. +- MeasGap: CU/DU coordinate dedicated `MeasGapConfig` in UE `MeasConfig`, DU + scheduler interrupts transmission (`nr_measgap_scheduling()`) for the UE to + apply the MeasGap configuration in connected mode for gap-based measurements that + are sent as `MeasurementReport` and processed at CU-CP (`rrc_gNB_process_MeasurementReport()`). + Event-driven reports (for example A3) are used by CU-side mobility logic and can + trigger handover procedures. + +### MeasGap + +```mermaid +sequenceDiagram + participant DM as Data model (neighbour_cell_configuration) + participant CU as CU-CP (RRC) + participant DU as DU (MAC/RRC) + participant UE as UE + + CU-->>DM: Read serving + neighbour frequency/PCI/band data + DM-->>CU: Provide neighbour/frequency inputs for MeasConfig + CU->>CU: nr_rrc_get_measconfig + Note over CU: Build UE MeasConfig + CU->>CU: get_meas_timing_config (cell.mtc, ue.measConfig) + Note over CU: Check distinct ssbFrequency + alt one frequency only + Note over CU: returns NULL, no meas_timing_config sent to DU + else multiple frequencies + CU->>DU: F1AP UE Context Setup Request + Note over CU,DU: Includes cu_to_du_rrc_info.meas_timing_config + DU->>DU: create_measgap_config + DU->>DU: encode_measgap_config + DU-->>CU: F1AP UE Context Setup Response + Note over DU,CU: Includes du_to_cu_rrc_info.meas_gap_config + CU->>CU: get_meas_gap_config + Note over CU: Decode/store gap in UE MeasConfig (UE.measConfig.measGapConfig) + CU->>UE: RRCReconfiguration + Note over CU,UE: Includes MeasConfig.measGapConfig + rect rgba(210, 235, 255, 0.35) + Note over DU,UE: Measurement-gap window + DU->>DU: nr_measgap_scheduling + Note over DU: Interrupt transmission for meas gap + UE->>UE: nr_rrc_handle_meas_indication + Note over UE: Perform gap-based neighbour/inter-frequency measurements + UE-->>CU: MeasurementReport + end + Note over UE,CU: UL-DCCH MeasurementReport for configured events (e.g. A3) + CU->>CU: rrc_gNB_process_MeasurementReport + Note over CU: Run mobility decision logic (may trigger handover) + end +``` + +### SIB3/SIB4 + +```mermaid +sequenceDiagram + participant DM as Data model (neighbour_cell_configuration) + participant CU as CU-CP (RRC) + participant DU as DU + participant UE as UE + + CU->>CU: cp_f1_served_cell_info_to_cell + CU->>DM: get_cell_neighbour_list + DM-->>CU: neighbour_cell_configuration for serving cell + CU->>CU: get_ssb_arfcn + Note over CU: Derive serving_ssb_arfcn from cell MTC + loop for each configured neighbour + Note over CU: Compare neighbour.absoluteFrequencySSB vs serving_ssb_arfcn + alt equal + CU->>CU: get_sib3_intra_freq_neighbors + else different + CU->>CU: get_sib4_inter_freq_neighbors + end + end + CU-->>DU: F1AP F1 Setup Response + DU-->>UE: BCCH-DL-SCH-Message + Note over DU,UE: Broadcast SystemInformation including configured SIB3/SIB4 + UE->>UE: nr_rrc_ue_decode_NR_BCCH_DL_SCH_Message + Note over UE: Evaluate idle/inactive reselection criteria and timers + Note over UE,DU: After reselection, access/registration continues on selected cell DU as needed +``` + ## UE Context UE context information is stored in `gNB_RRC_UE_t`, which includes: diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 46ff9f061c..e828001d1b 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -94,7 +94,81 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. deactivated, a transparent SDAP header is prepended to packets, but no further processing is being done. - `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission. - Currently, SIB2 is supported. + Currently supported: + - SIB2: serving-cell reselection parameters (configured in `sib2_config`) + - SIB3: intra-frequency neighbour cell list (neighbours on the same SSB ARFCN as the serving cell) + - SIB4: inter-frequency carriers + neighbour lists, grouped per `(absoluteFrequencySSB, subcarrierSpacing)`. Per-frequency fields (e.g. `cellReselectionPriority`, `threshX_HighP`, `threshX_LowP`, `q_OffsetFreq`) come from `frequency_list` + +Example activation: +``` +cu_sibs = ( 2, 3, 4 ); +``` +SIB2 is configured per-gNB in `sib2_config` (see below). SIB3/SIB4 are derived from the neighbour configuration: +- `neighbour_list` / `neighbour_cell_configuration`: neighbour identity + per-neighbour offsets (`q_OffsetCell`, etc.) +- `frequency_list`: per-frequency SIB4 reselection parameters (priority/thresholds/`q_OffsetFreq`) + +Example `gNBs.[0].sib2_config`: +``` +cu_sibs = ( 2 ); + +sib2_config : { + q_Hyst = 0; + cellReselectionPriority = 0; + threshServingLowP = 0; + threshServingLowQ = 4; + s_NonIntraSearchP = 10; + s_NonIntraSearchQ = 8; + q_RxLevMin = -56; + q_QualMin = -18; + s_IntraSearchP = 22; + s_IntraSearchQ = 20; + t_ReselectionNR = 1; + deriveSSB_IndexFromCell = 1; + + speed_t_Evaluation = 0; + speed_t_HystNormal = 0; + speed_n_CellChangeMedium = 1; + speed_n_CellChangeHigh = 2; + speed_sf_Medium = 1; + speed_sf_High = 0; +}; +``` + +#### SIB3/SIB4 and measurement gaps + +This section summarizes how SIB3/SIB4 and measurement gaps relate in NR and how OAI currently implements them. + +From 3GPP TS 38.331: + +- `SIB3` carries intra-frequency reselection information (`intraFreqNeighCellList`). +- `SIB4` carries inter-frequency reselection information (`interFreqCarrierFreqList` + and per-carrier neighbour lists). +- `MeasGapConfig` is part of dedicated `MeasConfig` (typically sent in + `RRCReconfiguration`) and controls measurement gaps in connected mode. + +In other words: + +- SIB3/SIB4 are broadcast SI for idle/inactive (`RRC_IDLE` / `RRC_INACTIVE`) + reselection behavior: UE performs autonomous cell reselection using broadcast + SI (SIB3 for intra-frequency, SIB4 for inter-frequency). +- `MeasGapConfig` is a dedicated UE measurement behavior and applies to + connected mode (`RRC_CONNECTED`): the network configures what the UE + measures and reports (periodic and event-based, e.g., A3), and those reports + are used by CU-CP mobility logic (including handover decisions). + +MeasGap does not depend on SIB3/SIB4, however they share the same underlying +neighbour/frequency data model, which is the common source of serving + +neighbour frequency information (see also [Neighbor-gNB configuration](#neighbour-gnb-configuration)): + +- SIB3/SIB4 generation is done on the CU-CP side (inside `rrc_gNB_du.c`) from + neighbour/frequency configuration and serving-cell MTC-derived ARFCN. +- Measurement-gap configuration also starts from the same neighbour/frequency + model: CU uses neighbour fields (frequency/PCI/band) to build UE + `MeasConfig` measurement objects, then DU derives/encodes gap parameters + from CU-provided timing (`meas_timing_config`) and returns `meas_gap_config` + for CU forwarding in dedicated `RRCReconfiguration`. + +Detailed implementation flow and sequence diagrams are documented in [`rrc-dev.md`](./rrc-dev.md). #### UE-specific configuration @@ -105,12 +179,64 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. Refer to the [handover tutorial](../handover-tutorial.md) for detailed information about gNB neighbors and handover procedures. +##### Configuration structure and key semantics + +The neighbour configuration is a 2-level structure: + +- Outer list: `neighbour_list` + - Key: `nr_cellid` of the serving cell + - One entry per serving cell +- Inner list: `neighbour_cell_configuration` + - Actual neighbour cells for that serving cell + - Contains neighbour fields such as `gNB_ID`, neighbour `nr_cellid`, `physical_cellId`, frequency, PLMN, etc. + +This same core configuration model is reused by multiple RRC procedures. + +- SIB3/SIB4 generation: uses neighbour identity/frequency/offset fields to derive + intra/inter-frequency SI. +- Connected-mode measurement config: uses neighbour frequency/PCI/band fields to build + UE `MeasConfig` measurement objects. +- Handover-related procedures: reuse neighbour identity fields (e.g., cell ID/PCI/PLMN + TAC, gNB ID) for target selection and for populating target-cell information carried + in NGAP handover messages. + +Conceptually, for each serving cell the RRC keeps: + +- A per-frequency table (`inter_freqs`): one entry per `(absoluteFrequencySSB, + subcarrierSpacing)` used for SIB4, containing the SIB4 per-frequency fields + (priority, thresholds, `q_OffsetFreq`, `q_RxLevMin`, `t_ReselectionNR`). +- A per-neighbour list (`neighbour_cells`): one entry per neighbour, with + identity (cell ID, PCI, PLMN, TAC), frequency (`absoluteFrequencySSB`, + `subcarrierSpacing`, `band`), and SIB3/SIB4 per-neighbour offsets + (`q_OffsetCell`, `q_RxLevMinOffsetCell`, `q_QualMinOffsetCell`). +- A link from neighbours to frequencies: each neighbour implicitly points to + the matching `inter_freqs` entry via its `(absoluteFrequencySSB, + subcarrierSpacing)`; if no such frequency exists, it is treated as having no + SIB4 per-frequency configuration. + +Notes: + +- In `neighbour_list`, only `nr_cellid` is used as the key for lookup. +- `physical_cellId` belongs to neighbour-cell entries in `neighbour_cell_configuration` (inner list). +- `nr_cellid` entries in `neighbour_list` should be unique to avoid ambiguous lookup. +- Intra-frequency neighbours (SIB3) are derived only from the per-cell + `neighbour_cell_configuration` on the serving carrier. + +At configuration time (`gnb_config.c`), neighbours are parsed into `neighbour_cells`, +per-neighbour SIB3/SIB4 offsets are validated, and a per-frequency array `inter_freqs` +is built by grouping neighbours by `(absoluteFrequencySSB, subcarrierSpacing)` and +in-range SIB4 per-frequency fields (`cellReselectionPriority`, `threshX_HighP/L`, +`q_OffsetFreq`) across neighbours on the same ARFCN. At SIB4 build time, the RRC uses +`inter_freqs` to create one `InterFreqCarrierFreqInfo` per ARFCN for inter-frequency +carriers (ARFCN different from the serving SSB ARFCN) and attaches all neighbours whose +`inter_freq_idx` points to that frequency entry. + ##### Required configuration parameters -To define a neighbor cell in the configuration file, the following parameters are required: +To define a neighbour cell in the configuration file, the following parameters are required: -- `gNB_ID` - identifier of the neighbor gNB (e.g., `0xe01`) -- `nr_cellid` - cell identifier of the neighbor cell (e.g., `11111111`) +- `gNB_ID` - identifier of the neighbour gNB (e.g., `0xe01`) +- `nr_cellid` - cell identifier of the neighbour cell (e.g., `11111111`) - `physical_cellId` - physical cell ID for radio identification (e.g., `1`) - `absoluteFrequencySSB` - SSB frequency in ARFCN notation (e.g., `643296`) - `subcarrierSpacing` - numerology index: 0=15kHz, 1=30kHz, 2=60kHz, 3=120kHz @@ -123,19 +249,45 @@ To define a neighbor cell in the configuration file, the following parameters ar Example configuration structure: ``` +# Per-frequency SIB4 configuration (one entry per ARFCN), shared by all cells +frequency_list = ( + { + absoluteFrequencySSB = 643296; + subcarrierSpacing = 1; # 30 kHz + band = 78; + + frequency_config = ( + { + cellReselectionPriority = 5; + threshX_HighP = 10; + threshX_LowP = 6; + q_OffsetFreq = 0; + # Optional: threshX_HighQ, threshX_LowQ, etc. + } + ); + } +); + +# Per-cell neighbour configuration; neighbours reference frequency_list via ARFCN/SCS neighbour_list = ( { nr_cellid = 12345678; + neighbour_cell_configuration = ( { gNB_ID = 0xe01; nr_cellid = 11111111; physical_cellId = 1; - absoluteFrequencySSB = 643296; - subcarrierSpacing = 1; # 30 kHz + absoluteFrequencySSB = 643296; # ARFCN used to look up matching entry in frequency_list + subcarrierSpacing = 1; # 30 kHz band = 78; plmn = { mcc = 001; mnc = 01; mnc_length = 2 }; tracking_area_code = 1; + + # Example per-neighbour offsets (SIB3/SIB4) + q_OffsetCell = 0; + q_RxLevMinOffsetCell = -1; + q_QualMinOffsetCell = -1; } ); } diff --git a/doc/handover-tutorial.md b/doc/handover-tutorial.md index 68e685c83d..02736da931 100644 --- a/doc/handover-tutorial.md +++ b/doc/handover-tutorial.md @@ -512,8 +512,16 @@ configuration, e.g. [neighbour-config-rfsim.conf](../../ci-scripts/conf_files/ne This configuration can also be present in a different file and included in the gNB configuration file with `@include "neighbour-config-rfsim.conf"`. -For each gNB there is a `neighbour_cell_configuration` linked to its serving -cell ID. +The neighbor configuration is nested: + +- `neighbour_list` outer entries are keyed by serving `nr_cellid` +- each outer entry contains `neighbour_cell_configuration`, i.e., the list of neighbor cells for that serving cell + +In this model: + +- outer `nr_cellid` entries should be unique +- neighbor `physical_cellId` values are defined in inner neighbor entries +- the same serving-cell keyed neighbor mapping is used for both F1 and N2 handover logic See the example above for `neighbour-config-ho.conf`. The same configuration is for both F1 and N2 handover.