Commit dc550eba authored by Robert Schmidt's avatar Robert Schmidt

Merge remote-tracking branch 'origin/rrc-sibs' into integration_2026_w16 (!3983)

Add SIB 3,4 support, configurable SIB2 and refactor CU/DU SIB management

This MR makes neighbour and inter-frequency configuration drive how
SIB2/SIB3/SIB4 are built and sent from CU to DUs. It standardizes SIB
payloads as byte_array_t with typed SIB IDs across RRC, F1AP and MAC,
reducing ad‑hoc buffer handling. Neighbour parsing, validation and
lookup are tightened.

Changes

- Minor refactor to gNB neighbour parsing and storage (shared PLMN
  extraction, safer allocation, etc).
- Represent SIB containers uniformly as byte_array_t plus nr_sib_type_t,
  and adapt F1AP, MAC and RRC users to the new container API.
- Make SIB2 cell-reselection information fully config-driven with
  explicit bounds checking and SIB2 ASN.1 building from that config.
- Generate SIB3 intra-frequency neighbours from the per-cell neighbour
  list and propagate them from CU to DU over F1, with MAC
  decoding/attaching them to SystemInformation.
- Generate SIB4 inter-frequency neighbours from a new frequency_list
  plus neighbour SIB3/SIB4 offsets, and propagate them from CU to DU
  over F1, with MAC decoding/attaching them.
- Add basic ASN.1 round-trip tests for SIB2/SIB3/SIB4 and SIB4 range
  checks, and update RRC docs to describe the neighbour/inter-frequency
  configuration model.

Testing:

1. in gNB conf file:

    cu_sibs = (2, 3, 4);

2. Update neighbour config file with SIB3/SIB4 conf:

    (see documentation)

3. run gNB and UE as usual

---

Logs & configs: see MR on Gitlab
parents 0b752116 22783bb3
...@@ -17,6 +17,7 @@ gNBs = ...@@ -17,6 +17,7 @@ gNBs =
// Tracking area code, 0x0000 and 0xfffe are reserved values // Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1; tracking_area_code = 1;
plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) }); plmn_list = ({ mcc = 208; mnc = 99; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
cu_sibs = ( 2, 3, 4 );
@include "neighbour-config.conf" @include "neighbour-config.conf"
nr_cellid = 12345678L; nr_cellid = 12345678L;
......
...@@ -5,6 +5,57 @@ ...@@ -5,6 +5,57 @@
# for the 2-cell rfsim setup (gNB_ID 0xe00 & 0xb00) # # 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 = ( neighbour_list = (
########################################################## ##########################################################
# Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) # # Entry USED BY gNB_ID = 0xe00 (nr_cellid = 1) #
...@@ -21,6 +72,9 @@ neighbour_list = ( ...@@ -21,6 +72,9 @@ neighbour_list = (
band = 78; band = 78;
plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
tracking_area_code = 1; tracking_area_code = 1;
q_OffsetCell = 2;
q_RxLevMinOffsetCell= 2;
q_QualMinOffsetCell = 1;
} }
); );
}, },
...@@ -40,6 +94,9 @@ neighbour_list = ( ...@@ -40,6 +94,9 @@ neighbour_list = (
band = 78; band = 78;
plmn = { mcc = 208; mnc = 99; mnc_length = 2 }; plmn = { mcc = 208; mnc = 99; mnc_length = 2 };
tracking_area_code = 1; tracking_area_code = 1;
q_OffsetCell = 2;
q_RxLevMinOffsetCell= 2;
q_QualMinOffsetCell = 1;
} }
); );
} }
......
...@@ -48,8 +48,8 @@ typedef union checkedparam { ...@@ -48,8 +48,8 @@ typedef union checkedparam {
int num_okintval; /* number of valid values in the checkingval array */ int num_okintval; /* number of valid values in the checkingval array */
} s1a; } s1a;
struct { struct {
int (*f2)(configmodule_interface_t *cfg, int (*f2)(const configmodule_interface_t *cfg,
paramdef_t *param); /* check an integer against an authorized range, defined by its min and max value */ 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 */ int okintrange[CONFIG_MAX_NUMCHECKVAL]; /* integer array, store min and max values */
} s2; } s2;
......
...@@ -183,14 +183,35 @@ int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param) ...@@ -183,14 +183,35 @@ int config_check_intval(configmodule_interface_t *cfg, paramdef_t *param)
return -1; return -1;
} }
for ( int i=0; i<param->chkPptr->s1.num_okintval ; i++) { if (param->type == TYPE_INT32 || param->type == TYPE_INT) {
if( *(param->uptr) == param->chkPptr->s1.okintval[i] ) { if (param->iptr == NULL) {
return 0; 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) int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param)
...@@ -211,15 +232,38 @@ int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param ...@@ -211,15 +232,38 @@ int config_check_modify_integer(configmodule_interface_t *cfg, paramdef_t *param
return -1; 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); UNUSED(cfg);
if( *(param->iptr) >= param->chkPptr->s2.okintrange[0] && *(param->iptr) <= param->chkPptr->s2.okintrange[1] ) { if( *(param->iptr) >= param->chkPptr->s2.okintrange[0] && *(param->iptr) <= param->chkPptr->s2.okintrange[1] ) {
return 0; return 0;
} }
fprintf(stderr,"[CONFIG] config_check_intrange: %s: %i invalid value, authorized range: %i %i\n", fprintf(stderr,
param->optname, (int)*(param->uptr), param->chkPptr->s2.okintrange[0], param->chkPptr->s2.okintrange[1]); "[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;
}
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; return -1;
} }
......
...@@ -51,7 +51,8 @@ int config_get_processedint(configmodule_interface_t *cfg, paramdef_t *cfgoption ...@@ -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 */ /* 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_intval(configmodule_interface_t *cfg, paramdef_t *param);
int config_check_modify_integer(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_check_strval(configmodule_interface_t *cfg, paramdef_t *param);
int config_checkstr_assign_integer(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); const paramdef_t *config_get_paramdef_from_name(const paramdef_t *pd, int num, const char *name);
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <stdlib.h> #include <stdlib.h>
#include "assertions.h" #include "assertions.h"
#include "common/utils/utils.h" #include "common/utils/utils.h"
#include "common/utils/ds/byte_array.h"
#define MAX_SI_GROUPS 3 #define MAX_SI_GROUPS 3
#define NR_MAX_PDSCH_TBS 3824 #define NR_MAX_PDSCH_TBS 3824
...@@ -79,10 +80,32 @@ static inline const char *rnti_types(nr_rnti_type_t rr) ...@@ -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)) // the total shift is 2 * 15, in dB scale thats 10log10(2^(15*2))
#define SQ15_SQUARED_NORM_FACTOR_DB 90.3089986992 #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 { typedef struct {
uint8_t *SIB_buffer; nr_sib_type_t SIB_type;
int SIB_size;
int SIB_type;
} nr_SIBs_t; } nr_SIBs_t;
typedef struct nr_bandentry_s { typedef struct nr_bandentry_s {
......
...@@ -154,7 +154,7 @@ These modes of operation are supported: ...@@ -154,7 +154,7 @@ These modes of operation are supported:
- RedCap SIB1 v17 IEs supported - RedCap SIB1 v17 IEs supported
- Coexistence of RedCap and Normal UEs - Coexistence of RedCap and Normal UEs
- Handling of RedCap capability for small PDCP/RLC SN size - 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 - NTN
- Support downlinkHARQ-FeedbackDisabled-r17 - Support downlinkHARQ-FeedbackDisabled-r17
- Support for 32 PDSCH and PUSCH HARQ processes per UE - Support for 32 PDSCH and PUSCH HARQ processes per UE
...@@ -194,7 +194,7 @@ These modes of operation are supported: ...@@ -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) - NR RRC (38.331) Rel 17 messages using new [asn1c](https://github.com/mouse07410/asn1c)
- LTE RRC (36.331) also updated to Rel 15 - 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) - 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) - 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 - Integration of RRC messages and procedures supporting UE 5G SA connection
...@@ -380,7 +380,7 @@ These modes of operation are supported: ...@@ -380,7 +380,7 @@ These modes of operation are supported:
- MIB processing - MIB processing
- Scheduling of system information block 1 (SIB1) reception - Scheduling of system information block 1 (SIB1) reception
* Other system information * 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) * Random access procedure (needs improvement, there is still not a clear separation between MAC and PHY)
- Mapping SSBs to multiple ROs - Mapping SSBs to multiple ROs
- Scheduling of PRACH - Scheduling of PRACH
......
...@@ -633,6 +633,98 @@ sequenceDiagram ...@@ -633,6 +633,98 @@ sequenceDiagram
Note over TargetCU: Then: encode Handover Command, send NGAP HANDOVER REQUEST ACKNOWLEDGE 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
UE context information is stored in `gNB_RRC_UE_t`, which includes: UE context information is stored in `gNB_RRC_UE_t`, which includes:
......
...@@ -94,7 +94,81 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. ...@@ -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 deactivated, a transparent SDAP header is prepended to packets, but no
further processing is being done. further processing is being done.
- `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission. - `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 #### UE-specific configuration
...@@ -105,12 +179,64 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB 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. 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 ##### 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`) - `gNB_ID` - identifier of the neighbour gNB (e.g., `0xe01`)
- `nr_cellid` - cell identifier of the neighbor cell (e.g., `11111111`) - `nr_cellid` - cell identifier of the neighbour cell (e.g., `11111111`)
- `physical_cellId` - physical cell ID for radio identification (e.g., `1`) - `physical_cellId` - physical cell ID for radio identification (e.g., `1`)
- `absoluteFrequencySSB` - SSB frequency in ARFCN notation (e.g., `643296`) - `absoluteFrequencySSB` - SSB frequency in ARFCN notation (e.g., `643296`)
- `subcarrierSpacing` - numerology index: 0=15kHz, 1=30kHz, 2=60kHz, 3=120kHz - `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 ...@@ -123,19 +249,45 @@ To define a neighbor cell in the configuration file, the following parameters ar
Example configuration structure: 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 = ( neighbour_list = (
{ {
nr_cellid = 12345678; nr_cellid = 12345678;
neighbour_cell_configuration = ( neighbour_cell_configuration = (
{ {
gNB_ID = 0xe01; gNB_ID = 0xe01;
nr_cellid = 11111111; nr_cellid = 11111111;
physical_cellId = 1; physical_cellId = 1;
absoluteFrequencySSB = 643296; absoluteFrequencySSB = 643296; # ARFCN used to look up matching entry in frequency_list
subcarrierSpacing = 1; # 30 kHz subcarrierSpacing = 1; # 30 kHz
band = 78; band = 78;
plmn = { mcc = 001; mnc = 01; mnc_length = 2 }; plmn = { mcc = 001; mnc = 01; mnc_length = 2 };
tracking_area_code = 1; tracking_area_code = 1;
# Example per-neighbour offsets (SIB3/SIB4)
q_OffsetCell = 0;
q_RxLevMinOffsetCell = -1;
q_QualMinOffsetCell = -1;
} }
); );
} }
......
...@@ -512,8 +512,16 @@ configuration, e.g. [neighbour-config-rfsim.conf](../../ci-scripts/conf_files/ne ...@@ -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 This configuration can also be present in a different file and included in the
gNB configuration file with `@include "neighbour-config-rfsim.conf"`. gNB configuration file with `@include "neighbour-config-rfsim.conf"`.
For each gNB there is a `neighbour_cell_configuration` linked to its serving The neighbor configuration is nested:
cell ID.
- `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 See the example above for `neighbour-config-ho.conf`. The same configuration
is for both F1 and N2 handover. is for both F1 and N2 handover.
...@@ -161,8 +161,7 @@ typedef struct f1ap_du_register_req_s { ...@@ -161,8 +161,7 @@ typedef struct f1ap_du_register_req_s {
typedef struct f1ap_sib_msg_s { typedef struct f1ap_sib_msg_s {
/// RRC container with system information owned by gNB-CU /// RRC container with system information owned by gNB-CU
uint8_t *SI_container; byte_array_t SI_container;
int SI_container_length;
/// SIB block type, e.g. 2 for sibType2 /// SIB block type, e.g. 2 for sibType2
int SI_type; int SI_type;
} f1ap_sib_msg_t; } f1ap_sib_msg_t;
......
...@@ -627,10 +627,10 @@ static void encode_cells_to_activate(const served_cells_to_activate_t *cell, F1A ...@@ -627,10 +627,10 @@ static void encode_cells_to_activate(const served_cells_to_activate_t *cell, F1A
F1AP_GNB_CUSystemInformation_t *gNB_CUSystemInformation = F1AP_GNB_CUSystemInformation_t *gNB_CUSystemInformation =
&cells_to_be_activated_itemExtIEs->extensionValue.choice.GNB_CUSystemInformation; &cells_to_be_activated_itemExtIEs->extensionValue.choice.GNB_CUSystemInformation;
const f1ap_sib_msg_t *SI_msg = &cell->SI_msg[n]; 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); asn1cSequenceAdd(gNB_CUSystemInformation->sibtypetobeupdatedlist.list, F1AP_SibtypetobeupdatedListItem_t, sib_item);
sib_item->sIBtype = SI_msg->SI_type; 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 ...@@ -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]; F1AP_SibtypetobeupdatedListItem_t *sib_item = gNB_CUSystemInformation->sibtypetobeupdatedlist.list.array[s];
/* SI container */ /* SI container */
f1ap_sib_msg_t *SI_msg = &out->SI_msg[s]; 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 */ /* SIB type */
SI_msg->SI_type = sib_item->sIBtype; 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 ...@@ -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++) { 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 *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]; 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); if (!eq_byte_array(&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); return false;
_EQ_CHECK_INT(a_SI_msg->SI_type, b_SI_msg->SI_type); _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) ...@@ -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++) { for (int j = 0; j < cp_cell->num_SI; j++) {
f1ap_sib_msg_t *cp_SI_msg = &cp_cell->SI_msg[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]; 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 = copy_byte_array(b_SI_msg->SI_container);
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_type = b_SI_msg->SI_type; 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) ...@@ -1221,8 +1219,7 @@ void free_f1ap_setup_response(const f1ap_setup_resp_t *msg)
free(msg->gNB_CU_name); free(msg->gNB_CU_name);
for (int i = 0; i < msg->num_cells_to_activate; i++) for (int i = 0; i < msg->num_cells_to_activate; i++)
for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) 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_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container);
free(msg->cells_to_activate[i].SI_msg[j].SI_container);
free(msg->cells_to_activate); free(msg->cells_to_activate);
} }
...@@ -1798,7 +1795,7 @@ void free_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t ...@@ -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 i = 0; i < msg->num_cells_to_activate; i++)
for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++)
free(msg->cells_to_activate[i].SI_msg[j].SI_container); free_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container);
free(msg->cells_to_activate); free(msg->cells_to_activate);
} }
...@@ -1819,8 +1816,8 @@ bool eq_f1ap_cu_configuration_update(const f1ap_gnb_cu_configuration_update_t *a ...@@ -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++) { 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 *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]; 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); if (!eq_byte_array(&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); return false;
_EQ_CHECK_INT(a_sib_msg->SI_type, b_sib_msg->SI_type); _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 ...@@ -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++) { 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]; 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]; 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)); SI_msg->SI_container = copy_byte_array(msg->cells_to_activate[i].SI_msg[s].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];
} }
} }
return cp; return cp;
...@@ -2153,8 +2148,8 @@ bool eq_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration ...@@ -2153,8 +2148,8 @@ bool eq_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configuration
return false; return false;
_EQ_CHECK_INT(a->cells_to_activate[i].num_SI, b->cells_to_activate[i].num_SI); _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++) { 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); if (!eq_byte_array(&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); 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); _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 ...@@ -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]; 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]; 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_type = msg_sib->SI_type;
cp_sib->SI_container_length = msg_sib->SI_container_length; cp_sib->SI_container = copy_byte_array(msg_sib->SI_container);
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);
} }
} }
return cp; return cp;
...@@ -2196,9 +2189,7 @@ void free_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configurati ...@@ -2196,9 +2189,7 @@ void free_f1ap_du_configuration_update_acknowledge(const f1ap_gnb_du_configurati
// SI_container // SI_container
for (int i = 0; i < msg->num_cells_to_activate; i++) { for (int i = 0; i < msg->num_cells_to_activate; i++) {
for (int j = 0; j < msg->cells_to_activate[i].num_SI; j++) { 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_byte_array(msg->cells_to_activate[i].SI_msg[j].SI_container);
free(msg->cells_to_activate[i].SI_msg[j].SI_container);
}
} }
} }
free(msg->cells_to_activate); free(msg->cells_to_activate);
......
...@@ -313,9 +313,9 @@ static void test_f1ap_setup_response(void) ...@@ -313,9 +313,9 @@ static void test_f1ap_setup_response(void)
int SI_container_length = strlen(s) + 1; int SI_container_length = strlen(s) + 1;
orig.cells_to_activate[0].num_SI = 1; orig.cells_to_activate[0].num_SI = 1;
f1ap_sib_msg_t *SI_msg = &orig.cells_to_activate[0].SI_msg[0]; f1ap_sib_msg_t *SI_msg = &orig.cells_to_activate[0].SI_msg[0];
SI_msg->SI_container = malloc_or_fail(SI_container_length); SI_msg->SI_container.buf = malloc_or_fail(SI_container_length);
memcpy(SI_msg->SI_container, (uint8_t *)s, SI_container_length); memcpy(SI_msg->SI_container.buf, s, SI_container_length);
SI_msg->SI_container_length = SI_container_length; SI_msg->SI_container.len = SI_container_length;
SI_msg->SI_type = 7; SI_msg->SI_type = 7;
} }
F1AP_F1AP_PDU_t *f1enc = encode_f1ap_setup_response(&orig); F1AP_F1AP_PDU_t *f1enc = encode_f1ap_setup_response(&orig);
...@@ -609,10 +609,10 @@ static void test_f1ap_du_configuration_update_acknowledge(void) ...@@ -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].plmn.mnc_digit_length = 2;
orig.cells_to_activate[0].num_SI = 1; 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_type = 7;
orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10; orig.cells_to_activate[0].SI_msg[0].SI_container.len = 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.buf = malloc(sizeof(uint8_t) * 10);
for (int i = 0; i < orig.cells_to_activate[0].SI_msg[0].SI_container_length; i++) { 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[i] = i; orig.cells_to_activate[0].SI_msg[0].SI_container.buf[i] = i;
} }
// ASN.1 enc/dec // ASN.1 enc/dec
F1AP_F1AP_PDU_t *f1enc = encode_f1ap_du_configuration_update_acknowledge(&orig); F1AP_F1AP_PDU_t *f1enc = encode_f1ap_du_configuration_update_acknowledge(&orig);
...@@ -655,8 +655,8 @@ static void test_f1ap_cu_configuration_update(void) ...@@ -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].plmn.mnc_digit_length = 2;
orig.cells_to_activate[0].num_SI = 1; 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_type = 7;
orig.cells_to_activate[0].SI_msg[0].SI_container_length = 10; orig.cells_to_activate[0].SI_msg[0].SI_container.len = 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.buf = malloc(sizeof(uint8_t) * 10);
F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update(&orig); F1AP_F1AP_PDU_t *f1enc = encode_f1ap_cu_configuration_update(&orig);
F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc);
f1ap_msg_free(f1enc); f1ap_msg_free(f1enc);
......
This diff is collapsed.
...@@ -31,19 +31,28 @@ uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uin ...@@ -31,19 +31,28 @@ uint16_t set_snssai_config(nssai_t *nssai, const int max_num_ssi, uint8_t k, uin
return num_ssi; 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,
.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) uint8_t set_plmn_config(plmn_id_t *p, uint8_t idx)
{ {
char gnbpath[MAX_OPTNAME_SIZE * 2 + 8]; char gnbpath[MAX_OPTNAME_SIZE * 2 + 8];
snprintf(gnbpath, sizeof(gnbpath), "%s.[%i]", GNB_CONFIG_STRING_GNB_LIST, idx); 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); GET_PARAMS_LIST(PLMNParamList, PLMNParams, GNBPLMNPARAMS_DESC, GNB_CONFIG_STRING_PLMN_LIST, gnbpath, PLMNPARAMS_CHECK);
uint8_t num_plmn = PLMNParamList.numelt; 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); 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) { for (int l = 0; l < num_plmn; ++l) {
plmn_id_t *plmn = &p[l]; p[l] = extract_plmn_from_params(PLMNParamList.paramarray[l], n_plmn_params);
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);
} }
return num_plmn; return num_plmn;
} }
...@@ -4,6 +4,8 @@ ...@@ -4,6 +4,8 @@
#include <assert.h> #include <assert.h>
#include "common/5g_platform_types.h" #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); 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); 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);
This diff is collapsed.
...@@ -988,27 +988,56 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ ...@@ -988,27 +988,56 @@ 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 NR_SystemInformation_IEs_t *sysInfov17 = calloc(1, sizeof(*sysInfov17)); // for othersibs
for (int i = 0; i < num_cu_sib; i++) { for (int i = 0; i < num_cu_sib; i++) {
config_sibs[i] = cu_sib[i].SI_type; 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]) { switch (config_sibs[i]) {
case 2: { case NR_SIB_2: {
struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc(1, sizeof(*type)); struct NR_SystemInformation_IEs__sib_TypeAndInfo__Member *type = calloc(1, sizeof(*type));
type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2; type->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib2;
// SIB2 coming from CU need to decode it // SIB2 coming from CU need to decode it
NR_SIB2_t *sib2 = NULL; NR_SIB2_t *sib2 = NULL;
asn_dec_rval_t dec_rval = uper_decode(NULL, asn_dec_rval_t dec_rval = uper_decode(NULL, &asn_DEF_NR_SIB2, (void **)&sib2, container->buf, container->len, 0, 0);
&asn_DEF_NR_SIB2,
(void **)&sib2,
cu_sib[i].SI_container,
cu_sib[i].SI_container_length,
0,
0);
if (dec_rval.code != RC_OK) { if (dec_rval.code != RC_OK) {
LOG_E(NR_MAC, "cannot decode SIB%d from CU\n", config_sibs[i]); 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; type->choice.sib2 = sib2;
add_sib_to_systeminformation(sysInfo, type); add_sib_to_systeminformation(sysInfo, type);
break; 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;
}
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 : default :
AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]); AssertFatal(false, "Invalid or not supported SIB%d\n", config_sibs[i]);
} }
...@@ -1019,7 +1048,7 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_ ...@@ -1019,7 +1048,7 @@ bool nr_mac_configure_other_sib(gNB_MAC_INST *nrmac, int num_cu_sib, const f1ap_
int sib_idx = i + num_cu_sib; int sib_idx = i + num_cu_sib;
config_sibs[sib_idx] = si->SIB_type; config_sibs[sib_idx] = si->SIB_type;
switch (config_sibs[sib_idx]) { 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)); 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; type_du->present = NR_SystemInformation_IEs__sib_TypeAndInfo__Member_PR_sib19_v1700;
NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon); NR_SIB19_r17_t *sib19 = get_SIB19_NR(cc->ServingCellConfigCommon);
......
...@@ -223,39 +223,67 @@ NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityR ...@@ -223,39 +223,67 @@ NR_UE_NR_Capability_t *decode_nr_ue_capability(int rnti, const NR_UE_CapabilityR
return NULL; return NULL;
} }
//------------------------------------------------------------------------------ byte_array_t do_SIB2_NR(const NR_SIB2_t *sib2)
{
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;
}
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;
}
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 do_SIB2_NR(uint8_t **msg_SIB2, NR_SSB_MTC_t *ssbmtc) 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;
}
byte_array_t do_SIB4_NR(NR_SIB4_t *sib4)
{ {
NR_SIB2_t *sib2 = calloc(1, sizeof(*sib2)); byte_array_t msg = {.buf = NULL, .len = 0};
sib2->cellReselectionInfoCommon.q_Hyst = NR_SIB2__cellReselectionInfoCommon__q_Hyst_dB0; char errbuf[256] = {0};
struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars *speed = calloc(1, sizeof(*speed)); size_t errlen = sizeof(errbuf);
NR_MobilityStateParameters_t mobilityStateParameters = {0}; int ret = asn_check_constraints(&asn_DEF_NR_SIB4, sib4, errbuf, &errlen);
mobilityStateParameters.t_Evaluation = NR_MobilityStateParameters__t_Evaluation_s30; if (ret != 0) {
mobilityStateParameters.t_HystNormal = NR_MobilityStateParameters__t_HystNormal_s30; LOG_E(NR_RRC, "SIB4 constraint check failed: %s\n", errbuf);
mobilityStateParameters.n_CellChangeMedium = 1; return msg;
mobilityStateParameters.n_CellChangeHigh = 2; }
speed->mobilityStateParameters = mobilityStateParameters;
struct NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF qhyst = {0}; int val = uper_encode_to_new_buffer(&asn_DEF_NR_SIB4, NULL, (void *)sib4, (void **)&msg.buf);
qhyst.sf_Medium = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_Medium_dB_4; if (val <= 0) {
qhyst.sf_High = NR_SIB2__cellReselectionInfoCommon__speedStateReselectionPars__q_HystSF__sf_High_dB_6; LOG_E(NR_RRC, "Failed to encode SIB4\n");
speed->q_HystSF = qhyst; return msg;
sib2->cellReselectionInfoCommon.speedStateReselectionPars = speed; }
sib2->cellReselectionServingFreqInfo.cellReselectionPriority = 0; // INTEGER (0..7)
sib2->cellReselectionServingFreqInfo.threshServingLowP = 0; msg.len = val;
NR_ReselectionThresholdQ_t *threshServingLowQ = calloc(1, sizeof(*threshServingLowQ)); return msg;
*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;
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 do_RRCReject(uint8_t *const buffer) int do_RRCReject(uint8_t *const buffer)
......
...@@ -33,6 +33,9 @@ ...@@ -33,6 +33,9 @@
#include "NR_MeasurementTimingConfiguration.h" #include "NR_MeasurementTimingConfiguration.h"
#include "NR_UE-NR-Capability.h" #include "NR_UE-NR-Capability.h"
#include "NR_UE-CapabilityRAT-ContainerList.h" #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/seq_arr.h"
#include "ds/byte_array.h" #include "ds/byte_array.h"
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h" #include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h"
...@@ -65,7 +68,9 @@ typedef struct { ...@@ -65,7 +68,9 @@ typedef struct {
*/ */
int xer_sprint_NR(char *string, size_t string_size, struct asn_TYPE_descriptor_s *td, void *sptr); 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);
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); int do_RRCReject(uint8_t *const buffer);
......
...@@ -14,12 +14,61 @@ extern "C" { ...@@ -14,12 +14,61 @@ extern "C" {
#include "NR_DRB-ToAddMod.h" #include "NR_DRB-ToAddMod.h"
#include "NR_DRB-ToAddModList.h" #include "NR_DRB-ToAddModList.h"
#include "NR_SRB-ToAddModList.h" #include "NR_SRB-ToAddModList.h"
#include "NR_InterFreqCarrierFreqInfo.h"
#include "ds/byte_array.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; RAN_CONTEXT_t RC;
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #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) TEST(nr_asn1, rrc_reject)
{ {
unsigned char buf[1000]; unsigned char buf[1000];
...@@ -138,6 +187,167 @@ TEST(nr_asn1, rrc_reconfiguration) ...@@ -138,6 +187,167 @@ TEST(nr_asn1, rrc_reconfiguration)
free_RRCReconfiguration_params(params); 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) int main(int argc, char **argv)
{ {
logInit(); logInit();
......
This diff is collapsed.
...@@ -263,10 +263,8 @@ static bool eq_pci(const void *vval, const void *vit) ...@@ -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) 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(cell);
DevAssert(head != NULL); elm_arr_t e = find_if((seq_arr_t *)&cell->neighbour_cells, &pci, eq_pci);
LOG_D(NR_RRC, "Number of neighbour cells: %ld\n", head->size);
elm_arr_t e = find_if(head, &pci, eq_pci);
if (e.found) { if (e.found) {
const nr_neighbour_cell_t *neighbour = (const nr_neighbour_cell_t *)e.it; 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); 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 ...@@ -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_MeasTimingList_t *mtlist = cell->mtc->criticalExtensions.choice.c1->choice.measTimingConf->measTiming;
const NR_MeasTiming_t *mt = mtlist->list.array[0]; 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); const neighbour_cell_configuration_t *neighbour_config = get_neighbour_cell_config(rrc, cell->info.cell_id);
seq_arr_t *neighbour_cells = NULL; const seq_arr_t *neighbour_cells = neighbour_config ? &neighbour_config->neighbour_cells : NULL;
if (neighbour_config)
neighbour_cells = neighbour_config->neighbour_cells;
int *neigh_a3_id = NULL; int *neigh_a3_id = NULL;
if (neighbour_cells && neighbour_cells->size) if (neighbour_cells && neighbour_cells->size)
neigh_a3_id = calloc_or_fail(neighbour_cells->size, sizeof(int)); 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 /* Loop through neighbours and find related A3 configuration
If no related A3 but there is default add the default one. 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. 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"); 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 bool default_a3_added = false; // To ensure that the default configuration is only added once
for (int i = 0; i < neighbour_cells->size; i++) { int i = 0;
nr_neighbour_cell_t *neighbourCell = (nr_neighbour_cell_t *)seq_arr_at(neighbour_cells, i); FOR_EACH_SEQ_ARR(nr_neighbour_cell_t *, neighbourCell, neighbour_cells) {
seq_arr_push_back(&neigh_seq, neighbourCell, sizeof(nr_neighbour_cell_t)); 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); const nr_a3_event_t *a3Event = get_a3_configuration((gNB_RRC_INST *)rrc, neighbourCell->physicalCellId);
if (!a3Event) { if (!a3Event) {
...@@ -814,12 +812,13 @@ NR_MeasConfig_t *nr_rrc_get_measconfig(const gNB_RRC_INST *rrc, uint64_t nr_cell ...@@ -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]; 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)); seq_arr_push_back(&rc_A3_seq, prepare_a3_event_report(a3Event, reportConfigId), sizeof(NR_ReportConfigToAddMod_t));
i++;
} }
} }
if (rrc->measurementConfiguration.per_event) if (meas_cfg->per_event)
rc_PER = prepare_periodic_event_report(rrc->measurementConfiguration.per_event); rc_PER = prepare_periodic_event_report(meas_cfg->per_event);
if (rrc->measurementConfiguration.a2_event) if (meas_cfg->a2_event)
rc_A2 = prepare_a2_event_report(rrc->measurementConfiguration.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); NR_MeasConfig_t *result = get_MeasConfig(mt, band, cell->info.pci, rc_PER, rc_A2, &rc_A3_seq, &neigh_seq, neigh_a3_id);
......
This diff is collapsed.
...@@ -607,7 +607,6 @@ void nr_HO_N2_trigger_telnet(gNB_RRC_INST *rrc, uint32_t neighbour_pci, uint32_t ...@@ -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) { 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); 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 = { nr_neighbour_cell_t neighbourConfig = {
.isIntraFrequencyNeighbour = true,
.gNB_ID = du->gNB_DU_id, .gNB_ID = du->gNB_DU_id,
.nrcell_id = pcell->info.cell_id, .nrcell_id = pcell->info.cell_id,
.physicalCellId = pcell->info.pci, .physicalCellId = pcell->info.pci,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment