From d38188e153d42adc1c203446b47a3c8fd913004e Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 21 Jan 2026 12:30:56 +0100 Subject: [PATCH 01/12] RRC: remove unused drbs configuration parameter - Delete drbs field from gNB_RrcConfigurationReq and its use in RCconfig_NRRRC() - Remove GNB_CONFIG_STRING_DRBS, help text, index, and GNBPARAMS_CHECK entry from gnb_paramdef.h - Drop DU-side verification and DRB count logging in gnb_config.c --- doc/RRC/rrc-usage.md | 2 -- openair2/COMMON/rrc_messages_types.h | 1 - openair2/GNB_APP/gnb_config.c | 3 --- openair2/GNB_APP/gnb_paramdef.h | 33 ++++++++++++---------------- 4 files changed, 14 insertions(+), 25 deletions(-) diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index bae31e864a..05f55d5123 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -94,8 +94,6 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. - `um_on_default_drb` (default: false): use RLC UM instead of RLC AM on default bearers -- `drbs` (default: 0): the number of DRBs to allocate for a UE, only useful for - do-ra or phy-test testing #### Neighbor-gNB configuration diff --git a/openair2/COMMON/rrc_messages_types.h b/openair2/COMMON/rrc_messages_types.h index cfc6914f4b..6b69959168 100644 --- a/openair2/COMMON/rrc_messages_types.h +++ b/openair2/COMMON/rrc_messages_types.h @@ -422,7 +422,6 @@ typedef struct NRRrcConfigurationReq_s { bool um_on_default_drb; bool enable_sdap; - int drbs; } gNB_RrcConfigurationReq; typedef struct NRDuDlReq_s { diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0b3845f5b9..01c44650ae 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -717,7 +717,6 @@ void RCconfig_verify(configmodule_interface_t *cfg, ngran_node_t node_type) } else if (NODE_IS_DU(node_type)) { // verify that there is no bearer config verify_gnb_param_notset(gnbp, GNB_ENABLE_SDAP_IDX, GNB_CONFIG_STRING_ENABLE_SDAP); - verify_gnb_param_notset(gnbp, GNB_DRBS, GNB_CONFIG_STRING_DRBS); verify_section_notset(cfg, GNB_CONFIG_STRING_GNB_LIST ".[0]", GNB_CONFIG_STRING_AMF_IP_ADDRESS); verify_section_notset(cfg, NULL, CONFIG_STRING_SECURITY); @@ -2092,9 +2091,7 @@ gNB_RRC_INST *RCconfig_NRRRC() nrrrc_config.num_plmn = set_plmn_config(nrrrc_config.plmn, k); nrrrc_config.enable_sdap = *GNBParamList.paramarray[i][GNB_ENABLE_SDAP_IDX].iptr; LOG_I(GNB_APP, "SDAP layer is %s\n", nrrrc_config.enable_sdap ? "enabled" : "disabled"); - nrrrc_config.drbs = *GNBParamList.paramarray[i][GNB_DRBS].iptr; nrrrc_config.um_on_default_drb = *(GNBParamList.paramarray[i][GNB_UMONDEFAULTDRB_IDX].uptr); - LOG_I(GNB_APP, "Data Radio Bearer count %d\n", nrrrc_config.drbs); }// }//End for (k=0; k Date: Wed, 17 Sep 2025 11:42:39 +0200 Subject: [PATCH 02/12] E1AP: add DRB To Remove list to E1 Bearer Context Modification lib Add DRB To Remove List IE to PDU Session To Modify list, which is sent in the Bearer Context Modification request. This allows the CUCP to inform the CUUP about DRBs to be released in a specific PDU session during a MODIFY procedure. Changes: * Add struct definition * Add enc/dec/eq/cp func * Add test --- openair2/COMMON/e1ap_messages_types.h | 9 +++++++ .../E1AP/lib/e1ap_bearer_context_management.c | 24 +++++++++++++++++++ openair2/E1AP/lib/e1ap_lib_includes.h | 2 ++ openair2/E1AP/tests/e1ap_lib_test.c | 2 ++ openair2/RRC/NR/rrc_gNB.c | 1 + 5 files changed, 38 insertions(+) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index 711c126dab..01a5d6b260 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -409,6 +409,12 @@ typedef struct DRB_nGRAN_to_modify_s { qos_flow_to_setup_t qosFlows[E1AP_MAX_NUM_QOS_FLOWS]; } DRB_nGRAN_to_mod_t; +/* DRB To Remove Item (NG-RAN) clause 9.3.1.11 */ +typedef struct { + // DRB ID (M) + long id; +} drb_to_remove_t; + typedef enum e1ap_indication_e { SECURITY_REQUIRED = 0, SECURITY_PREFERRED, @@ -472,6 +478,9 @@ typedef struct pdu_session_to_mod_s { long numDRB2Modify; // DRB To Modify Item (1..) DRB_nGRAN_to_mod_t DRBnGRanModList[E1AP_MAX_NUM_DRBS]; + // DRB To Remove List (0..maxnoofDRBs) + int n_drb_to_remove; + drb_to_remove_t drbs_to_remove[E1AP_MAX_NUM_DRBS]; } pdu_session_to_mod_t; /** PDU Session Resource To Remove List (3GPP TS 38.463 clause 9.3.3.12) */ diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index f2593f103c..95245d8492 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -1465,6 +1465,14 @@ static E1AP_PDU_Session_Resource_To_Modify_Item_t e1_encode_pdu_session_to_mod_i *ie1_2 = e1_encode_qos_flow_to_setup(k); } } + // DRB To Remove List (O) + if (in->n_drb_to_remove > 0) { + asn1cCalloc(out.dRB_To_Remove_List_NG_RAN, drb2Remove_List); + for (int r = 0; r < in->n_drb_to_remove; r++) { + asn1cSequenceAdd(drb2Remove_List->list, E1AP_DRB_To_Remove_Item_NG_RAN_t, drb2Rem); + drb2Rem->dRB_ID = in->drbs_to_remove[r].id; + } + } return out; } @@ -1746,6 +1754,15 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E } } } + // DRB To Remove List (O) + if (in->dRB_To_Remove_List_NG_RAN) { + E1AP_DRB_To_Remove_List_NG_RAN_t *rm = in->dRB_To_Remove_List_NG_RAN; + out->n_drb_to_remove = rm->list.count; + for (int r = 0; r < rm->list.count; r++) { + E1AP_DRB_To_Remove_Item_NG_RAN_t *item = rm->list.array[r]; + out->drbs_to_remove[r].id = item->dRB_ID; + } + } return true; } @@ -1931,6 +1948,9 @@ static pdu_session_to_mod_t cp_pdu_session_to_mod_item(const pdu_session_to_mod_ // DRB to modify list for (int j = 0; j < msg->numDRB2Modify; j++) cp.DRBnGRanModList[j] = cp_drb_to_mod_item(&msg->DRBnGRanModList[j]); + // DRB to remove list + for (int r = 0; r < msg->n_drb_to_remove; r++) + cp.drbs_to_remove[r] = msg->drbs_to_remove[r]; _E1_CP_OPTIONAL_IE(&cp, msg, securityIndication); _E1_CP_OPTIONAL_IE(&cp, msg, UP_TL_information); return cp; @@ -2021,6 +2041,10 @@ static bool eq_pdu_session_to_mod_item(const pdu_session_to_mod_t *a, const pdu_ return false; } _E1_EQ_CHECK_OPTIONAL_PTR(a, b, securityIndication); + _E1_EQ_CHECK_INT(a->n_drb_to_remove, b->n_drb_to_remove); + for (int r = 0; r < a->n_drb_to_remove; r++) { + _E1_EQ_CHECK_LONG(a->drbs_to_remove[r].id, b->drbs_to_remove[r].id); + } if (a->securityIndication && b->securityIndication) { if (!eq_security_ind(a->securityIndication, b->securityIndication)) return false; diff --git a/openair2/E1AP/lib/e1ap_lib_includes.h b/openair2/E1AP/lib/e1ap_lib_includes.h index adafbe0ae5..f58875a306 100644 --- a/openair2/E1AP/lib/e1ap_lib_includes.h +++ b/openair2/E1AP/lib/e1ap_lib_includes.h @@ -76,6 +76,8 @@ #include "E1AP_PDU-Session-Resource-To-Modify-Item.h" #include "E1AP_DRB-To-Modify-List-NG-RAN.h" #include "E1AP_DRB-To-Modify-Item-NG-RAN.h" +#include "E1AP_DRB-To-Remove-List-NG-RAN.h" +#include "E1AP_DRB-To-Remove-Item-NG-RAN.h" #include "E1AP_PDU-Session-Resource-To-Setup-Mod-Item.h" #include "E1AP_DRB-To-Setup-Mod-Item-NG-RAN.h" #include "E1AP_PDU-Session-Resource-To-Remove-Item.h" diff --git a/openair2/E1AP/tests/e1ap_lib_test.c b/openair2/E1AP/tests/e1ap_lib_test.c index 50116514fb..e11cacc241 100644 --- a/openair2/E1AP/tests/e1ap_lib_test.c +++ b/openair2/E1AP/tests/e1ap_lib_test.c @@ -515,6 +515,8 @@ static void test_bearer_context_modification_request(void) .sessionId = 1, .numDRB2Modify = 1, .DRBnGRanModList[0] = drb_to_mod, + .n_drb_to_remove = 1, + .drbs_to_remove[0].id = 2, }; DRB_nGRAN_to_setup_t drb_to_setup = { diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 6b914c27e9..2434cabba3 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2879,6 +2879,7 @@ static int fill_drb_to_be_setup_from_e1_resp(const gNB_RRC_INST *rrc, /* pass QoS info to MAC */ int nb_qos_flows = drb_config->numQosFlowSetup; AssertFatal(nb_qos_flows > 0, "must map at least one flow to a DRB\n"); + DevAssert(nb_qos_flows <= MAX_QOS_FLOWS); drb->nr.flows_len = nb_qos_flows; drb->nr.flows = calloc_or_fail(nb_qos_flows, sizeof(*drb->nr.flows)); for (int j = 0; j < nb_qos_flows; j++) { From c679b5953b494124735dae74c3fa33dba1efc7e4 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 9 Dec 2025 16:18:58 +0100 Subject: [PATCH 03/12] E1AP: rename numQosFlow2Setup to numQosFlowsMod in DRB To Modify List A DRB To Modify Item contains a Flow Mapping Information, which overrides previous mapping information. For consistency, rename the count member to reflect a QoS Flow modification, since it is not limited to setup only. - Rename DRB flow mapping counters to `numQosFlowsMod` for a DRB To Modify List - Adjust encode/decode/equality + E1AP tests --- openair2/COMMON/e1ap_messages_types.h | 4 ++-- openair2/E1AP/lib/e1ap_bearer_context_management.c | 8 ++++---- openair2/E1AP/tests/e1ap_lib_test.c | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index 01a5d6b260..37f29c1f4c 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -404,8 +404,8 @@ typedef struct DRB_nGRAN_to_modify_s { // DL UP Transport Layer Information (O) (clause 9.3.1.13, 9.3.2.1) int numDlUpParam; up_params_t DlUpParamList[E1AP_MAX_NUM_UP_PARAM]; - // QoS Flows Information To Modify (O) (clause 9.3.1.25, 9.3.1.26) - int numQosFlow2Setup; + // Flow Mapping Information (O) (clause 9.3.1.25, 9.3.1.26) + int numQosFlowsMod; qos_flow_to_setup_t qosFlows[E1AP_MAX_NUM_QOS_FLOWS]; } DRB_nGRAN_to_mod_t; diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index 95245d8492..ee832ef85a 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -1459,7 +1459,7 @@ static E1AP_PDU_Session_Resource_To_Modify_Item_t e1_encode_pdu_session_to_mod_i } } // QoS Flows to setup (O) - for (const qos_flow_to_setup_t *k = j->qosFlows; k < j->qosFlows + j->numQosFlow2Setup; k++) { + for (const qos_flow_to_setup_t *k = j->qosFlows; k < j->qosFlows + j->numQosFlowsMod; k++) { asn1cCalloc(drb2Mod->flow_Mapping_Information, flow_Mapping_Information); asn1cSequenceAdd(flow_Mapping_Information->list, E1AP_QoS_Flow_QoS_Parameter_Item_t, ie1_2); *ie1_2 = e1_encode_qos_flow_to_setup(k); @@ -1748,7 +1748,7 @@ static bool e1_decode_pdu_session_to_mod_item(pdu_session_to_mod_t *out, const E // QoS Flows Information To Be Setup (O) if (drb2Mod->flow_Mapping_Information) { E1AP_QoS_Flow_QoS_Parameter_List_t *qos2SetupList = drb2Mod->flow_Mapping_Information; - drb->numQosFlow2Setup = qos2SetupList->list.count; + drb->numQosFlowsMod = qos2SetupList->list.count; for (int k = 0; k < qos2SetupList->list.count; k++) { CHECK_E1AP_DEC(e1_decode_qos_flow_to_setup(drb->qosFlows + k, qos2SetupList->list.array[k])); } @@ -1992,8 +1992,8 @@ static bool eq_security_ind(security_indication_t *a, security_indication_t *b) static bool eq_drb_to_mod(const DRB_nGRAN_to_mod_t *a, const DRB_nGRAN_to_mod_t *b) { _E1_EQ_CHECK_LONG(a->id, b->id); - _E1_EQ_CHECK_INT(a->numQosFlow2Setup, b->numQosFlow2Setup); - for (int i = 0; i < a->numQosFlow2Setup; i++) { + _E1_EQ_CHECK_INT(a->numQosFlowsMod, b->numQosFlowsMod); + for (int i = 0; i < a->numQosFlowsMod; i++) { if (!eq_qos_flow(&a->qosFlows[i], &b->qosFlows[i])) return false; } diff --git a/openair2/E1AP/tests/e1ap_lib_test.c b/openair2/E1AP/tests/e1ap_lib_test.c index e11cacc241..6a94e0df23 100644 --- a/openair2/E1AP/tests/e1ap_lib_test.c +++ b/openair2/E1AP/tests/e1ap_lib_test.c @@ -505,7 +505,7 @@ static void test_bearer_context_modification_request(void) .pdcp_config = malloc_or_fail(sizeof(*drb_to_mod.pdcp_config)), .pdcp_sn_status_requested = true, .pdcp_status = malloc_or_fail(sizeof(*drb_to_mod.pdcp_status)), - .numQosFlow2Setup = 1, + .numQosFlowsMod = 1, .qosFlows[0] = dummy_qos_flows, }; *drb_to_mod.pdcp_config = dummy_pdcp_config; From fa0542f845ae9968a11025406552bd1e0570f899 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Mon, 1 Dec 2025 11:38:24 +0100 Subject: [PATCH 04/12] NGAP: fix NGAP PDU Session Modify transfer encoding Encode response/unsuccessful transfers using existing stack-allocated transfer structs. --- openair3/NGAP/ngap_gNB_nas_procedures.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/openair3/NGAP/ngap_gNB_nas_procedures.c b/openair3/NGAP/ngap_gNB_nas_procedures.c index 7bf7753668..f24acd79ff 100644 --- a/openair3/NGAP/ngap_gNB_nas_procedures.c +++ b/openair3/NGAP/ngap_gNB_nas_procedures.c @@ -1115,12 +1115,11 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ qos->qosFlowIdentifier = pdusession_modify_resp_p->pdusessions[i].qos[qos_flow_index].qfi; } asn_encode_to_new_buffer_result_t res = {0}; - NGAP_PDUSessionResourceModifyResponseTransfer_t *transfer_p = NULL; - res = asn_encode_to_new_buffer(NULL, ATS_ALIGNED_CANONICAL_PER, &asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, transfer_p); + res = asn_encode_to_new_buffer(NULL, ATS_ALIGNED_CANONICAL_PER, &asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, &transfer); item->pDUSessionResourceModifyResponseTransfer.buf = res.buffer; item->pDUSessionResourceModifyResponseTransfer.size = res.result.encoded; - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, transfer_p); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyResponseTransfer, &transfer); NGAP_DEBUG("pdusession_modify_resp: modified pdusession ID %ld\n", item->pDUSessionID); } @@ -1143,12 +1142,11 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ encode_ngap_cause(&pdusessionTransfer.cause, &pdusession_modify_resp_p->pdusessions_failed[i].cause); asn_encode_to_new_buffer_result_t res = {0}; - NGAP_PDUSessionResourceModifyUnsuccessfulTransfer_t *pdusessionTransfer_p = NULL; res = asn_encode_to_new_buffer(NULL, ATS_ALIGNED_CANONICAL_PER, &asn_DEF_NGAP_PDUSessionResourceModifyUnsuccessfulTransfer, &pdusessionTransfer); item->pDUSessionResourceModifyUnsuccessfulTransfer.buf = res.buffer; item->pDUSessionResourceModifyUnsuccessfulTransfer.size = res.result.encoded; - ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyUnsuccessfulTransfer, pdusessionTransfer_p); + ASN_STRUCT_FREE_CONTENTS_ONLY(asn_DEF_NGAP_PDUSessionResourceModifyUnsuccessfulTransfer, &pdusessionTransfer); NGAP_INFO("pdusession_modify_resp: failed pdusession ID %ld\n", item->pDUSessionID); } From 53c0d2d30643be52e744c7df12bf9f3579214568 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 22 Apr 2025 23:51:00 +0200 Subject: [PATCH 05/12] NGAP: refactor PDU Session Resource Modify struct definition This commit refactors the PDU Session Resource Modify Response message structures to improve naming, documentation, and compliance with 3GPP TS 38.413 9.2.1.6. Changes in NGAP: - Rename qos_flow_tobe_modified_t to qos_flow_addmod_response_item_t to better reflect its purpose as a QoS Flow Add or Modify Response Item IE (as per specs) - Add mandatory AMF UE NGAP ID field to ngap_pdusession_modify_resp_t structure - Optimize field types: change nb_of_pdusessions and nb_of_pdusessions_failed from uint16_t to uint8_t (NGAP_MAX_PDU_SESSION limit) (ref 3GPP TS 38.413, 9.2.1.6) - Update PDU Session Modify Response encoder to use amf_ue_ngap_id from the message structure (pdusession_modify_resp_p->amf_ue_ngap_id) instead of retrieving it from UE context (ue_context_p->amf_ue_ngap_id) - Add comprehensive comments with 3GPP TS 38.413 section references throughout Changes in RRC/NGAP: - Populate amf_ue_ngap_id field in PDU Session Modify Response message from UE context - Update type reference to qos_flow_addmod_response_item_t --- openair2/COMMON/ngap_messages_types.h | 27 ++++++++++++++----------- openair2/RRC/NR/rrc_gNB_NGAP.c | 3 ++- openair3/NGAP/ngap_gNB_nas_procedures.c | 2 +- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index b7c6c281ec..111a131f39 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -226,18 +226,21 @@ typedef struct pdusession_setup_s { pdusession_associate_qosflow_t associated_qos_flows[MAX_QOS_FLOWS]; } pdusession_setup_t; +/* QoS Flow Add or Modify Response Item (3GPP TS 38.413 9.2.1.6) */ typedef struct qos_flow_tobe_modified_s { - uint8_t qfi; // 0~63 -} qos_flow_tobe_modified_t; + // QoS Flow Identifier + uint8_t qfi; +} qos_flow_addmod_response_item_t; +/* PDU Session Resource Modify Response Item (3GPP TS 38.413 9.2.1.6) */ typedef struct pdusession_modify_s { - /* Unique pdusession_id for the UE. */ + // PDU Session ID uint8_t pdusession_id; - + /* PDU Session Resource Modify Response Transfer */ + // QoS Flow Add or Modify Response List uint8_t nb_of_qos_flow; - // qos_flow_add_or_modify - qos_flow_tobe_modified_t qos[MAX_QOS_FLOWS]; + qos_flow_addmod_response_item_t qos[MAX_QOS_FLOWS]; } pdusession_modify_t; /* Cause (9.3.1.2 of 3GPP TS 38.413) */ @@ -889,17 +892,17 @@ typedef struct ngap_pdusession_modify_req_s { pdusession_resource_item_t pdusession[NGAP_MAX_PDU_SESSION]; } ngap_pdusession_modify_req_t; +/* 9.2.1.6 of 3GPP TS 38.413 */ typedef struct ngap_pdusession_modify_resp_s { + // RAN UE NGAP ID uint32_t gNB_ue_ngap_id; - - /* Number of pdusession modify-ed in the list */ + // AMF UE NGAP ID + uint64_t amf_ue_ngap_id; + // PDU Session Resource Modify Response List (0..256) uint16_t nb_of_pdusessions; - /* list of pdusession modify-ed by RRC layers */ pdusession_modify_t pdusessions[NGAP_MAX_PDU_SESSION]; - - /* Number of pdusession failed to be modify in list */ + // PDU Session Resource Failed to Modify List (0..256) uint16_t nb_of_pdusessions_failed; - /* list of pdusessions that failed to be modify */ pdusession_failed_t pdusessions_failed[NGAP_MAX_PDU_SESSION]; } ngap_pdusession_modify_resp_t; diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index bf8a797a49..3792b0e999 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -993,6 +993,7 @@ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE LOG_I(NR_RRC, "send message NGAP_PDUSESSION_MODIFY_RESP \n"); resp->gNB_ue_ngap_id = UE->rrc_ue_id; + resp->amf_ue_ngap_id = UE->amf_ue_ngap_id; FOR_EACH_SEQ_ARR(rrc_pdu_session_param_t *, session, &UE->pduSessions) { if (xid != session->xid) { @@ -1015,7 +1016,7 @@ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE p->pdusession_id = session->param.pdusession_id; FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, qos_session, &session->param.qos) { DevAssert(p->nb_of_qos_flow < MAX_QOS_FLOWS); - qos_flow_tobe_modified_t *q = &p->qos[p->nb_of_qos_flow++]; + qos_flow_addmod_response_item_t *q = &p->qos[p->nb_of_qos_flow++]; q->qfi = qos_session->qos.qfi; } LOG_I(NR_RRC, diff --git a/openair3/NGAP/ngap_gNB_nas_procedures.c b/openair3/NGAP/ngap_gNB_nas_procedures.c index f24acd79ff..8a2cfeead5 100644 --- a/openair3/NGAP/ngap_gNB_nas_procedures.c +++ b/openair3/NGAP/ngap_gNB_nas_procedures.c @@ -1084,7 +1084,7 @@ int ngap_gNB_pdusession_modify_resp(instance_t instance, ngap_pdusession_modify_ ie->id = NGAP_ProtocolIE_ID_id_AMF_UE_NGAP_ID; ie->criticality = NGAP_Criticality_ignore; ie->value.present = NGAP_PDUSessionResourceModifyResponseIEs__value_PR_AMF_UE_NGAP_ID; - asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, ue_context_p->amf_ue_ngap_id); + asn_uint642INTEGER(&ie->value.choice.AMF_UE_NGAP_ID, pdusession_modify_resp_p->amf_ue_ngap_id); asn1cSeqAdd(&out->protocolIEs.list, ie); } /* mandatory */ From 2923e50ed0dcae12a565a11609e4f6fe5c8f7e05 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 12 Dec 2025 15:39:23 +0100 Subject: [PATCH 06/12] RRC/E1AP: Fix SDAP header mapping in E1AP and test callback header handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit fixes two related issues: 1. SDAP Header Mapping Bug (cucp_cuup_handler.c): The original code incorrectly mapped SDAP header configuration from internal E1AP representation to ASN.1 enum values. The internal representation (`bearer_context_sdap_config_t`) uses true=present/false=absent, while ASN.1 enum (`E1AP_SDAP_Configuration_t`) uses 0=present/1=absent. The fix correctly inverts the mapping: - Internal true (1) → ASN.1 0 (present) - Internal false (0) → ASN.1 1 (absent) The commit also clarifies internal representation in the stack. 2. Test Overhead Adjustment (nr-cuup-load-test.c): The change in (1) led to a failure in nr_cuup_functional_test with assertion: "size % 4 == 0" in recv_ng() - Changed UL packet overhead from 3 bytes to 2 bytes in sender_thread() - Before the SDAP fix, the test incorrectly accounted for 3 bytes: * 2 bytes PDCP header (correct) * 1 byte SDAP header (incorrectly included) - After the SDAP fix, SDAP headers are correctly set to absent, so only 2 bytes (PDCP header) are needed This ensures the test sends packets with the correct header size, matching what CU-UP expects after the SDAP configuration fix. The load test does not enable SDAP headers, therefore the original 3-byte overhead was possibly a workaround for the SDAP header bug. With the bug fixed, the test correctly uses 2-byte overhead (PDCP header). --- openair2/COMMON/e1ap_messages_types.h | 4 ++-- openair2/E1AP/lib/e1ap_bearer_context_management.c | 12 ++++++------ openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 7 +++++-- openair2/RRC/NR/rrc_gNB_NGAP.c | 4 ++-- tests/nr-cuup/nr-cuup-load-test.c | 4 +++- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/openair2/COMMON/e1ap_messages_types.h b/openair2/COMMON/e1ap_messages_types.h index 37f29c1f4c..48cfef98f3 100644 --- a/openair2/COMMON/e1ap_messages_types.h +++ b/openair2/COMMON/e1ap_messages_types.h @@ -268,8 +268,8 @@ typedef struct up_params_s { /* IE SDAP Configuration (clause 9.3.1.39 of 3GPP TS 38.463) */ typedef struct bearer_context_sdap_config_s { long defaultDRB; - long sDAP_Header_UL; - long sDAP_Header_DL; + bool sDAP_Header_UL; + bool sDAP_Header_DL; } bearer_context_sdap_config_t; /* IE PDCP Configuration (clause 9.3.1.38 of 3GPP TS 38.463) */ diff --git a/openair2/E1AP/lib/e1ap_bearer_context_management.c b/openair2/E1AP/lib/e1ap_bearer_context_management.c index ee832ef85a..15c472b10d 100644 --- a/openair2/E1AP/lib/e1ap_bearer_context_management.c +++ b/openair2/E1AP/lib/e1ap_bearer_context_management.c @@ -192,8 +192,8 @@ static E1AP_SDAP_Configuration_t e1_encode_sdap_config(const bearer_context_sdap { E1AP_SDAP_Configuration_t out = {0}; out.defaultDRB = in->defaultDRB ? E1AP_DefaultDRB_true : E1AP_DefaultDRB_false; - out.sDAP_Header_UL = in->sDAP_Header_UL; - out.sDAP_Header_DL = in->sDAP_Header_DL; + out.sDAP_Header_UL = in->sDAP_Header_UL ? E1AP_SDAP_Header_UL_present : E1AP_SDAP_Header_UL_absent; + out.sDAP_Header_DL = in->sDAP_Header_DL ? E1AP_SDAP_Header_DL_present : E1AP_SDAP_Header_DL_absent; return out; } @@ -201,8 +201,8 @@ static E1AP_SDAP_Configuration_t e1_encode_sdap_config(const bearer_context_sdap static bool e1_decode_sdap_config(bearer_context_sdap_config_t *out, const E1AP_SDAP_Configuration_t *in) { out->defaultDRB = in->defaultDRB == E1AP_DefaultDRB_true; - out->sDAP_Header_UL = in->sDAP_Header_UL; - out->sDAP_Header_DL = in->sDAP_Header_DL; + out->sDAP_Header_UL = in->sDAP_Header_UL == E1AP_SDAP_Header_UL_present; + out->sDAP_Header_DL = in->sDAP_Header_DL == E1AP_SDAP_Header_DL_present; return true; } @@ -212,8 +212,8 @@ static bool e1_decode_sdap_config(bearer_context_sdap_config_t *out, const E1AP_ static bool eq_sdap_config(const bearer_context_sdap_config_t *a, const bearer_context_sdap_config_t *b) { _E1_EQ_CHECK_LONG(a->defaultDRB, b->defaultDRB); - _E1_EQ_CHECK_LONG(a->sDAP_Header_UL, b->sDAP_Header_UL); - _E1_EQ_CHECK_LONG(a->sDAP_Header_DL, b->sDAP_Header_DL); + _E1_EQ_CHECK_INT(a->sDAP_Header_UL, b->sDAP_Header_UL); + _E1_EQ_CHECK_INT(a->sDAP_Header_DL, b->sDAP_Header_DL); return true; } diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index ecdf3dfd72..feb4ea3eff 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -60,8 +60,11 @@ static void fill_DRB_configList_e1(NR_DRB_ToAddModList_t *DRB_configList, const asn1cCalloc(ie->cnAssociation->choice.sdap_Config, sdap_config); sdap_config->pdu_Session = pdu->sessionId; /* SDAP */ - sdap_config->sdap_HeaderDL = drb->sdap_config.sDAP_Header_DL; - sdap_config->sdap_HeaderUL = drb->sdap_config.sDAP_Header_UL; + /* Convert from internal representation to ASN.1 enum: + * Internal: false=absent, true=present (or uninitialized=false means absent) + * ASN.1: 0=present, 1=absent */ + sdap_config->sdap_HeaderDL = drb->sdap_config.sDAP_Header_DL ? NR_SDAP_Config__sdap_HeaderDL_present : NR_SDAP_Config__sdap_HeaderDL_absent; + sdap_config->sdap_HeaderUL = drb->sdap_config.sDAP_Header_UL ? NR_SDAP_Config__sdap_HeaderUL_present : NR_SDAP_Config__sdap_HeaderUL_absent; sdap_config->defaultDRB = drb->sdap_config.defaultDRB; asn1cCalloc(sdap_config->mappedQoS_FlowsToAdd, FlowsToAdd); for (int j = 0; j < drb->numQosFlow2Setup; j++) { diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 3792b0e999..ce87d56661 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -299,8 +299,8 @@ static DRB_nGRAN_to_setup_t fill_e1_drb_to_setup(const drb_t *rrc_drb, drb_ngran.id = rrc_drb->drb_id; drb_ngran.sdap_config.defaultDRB = true; - drb_ngran.sdap_config.sDAP_Header_UL = session->sdap_config.header_ul_absent ? 1 : 0; - drb_ngran.sdap_config.sDAP_Header_DL = session->sdap_config.header_dl_absent ? 1 : 0; + drb_ngran.sdap_config.sDAP_Header_UL = session->sdap_config.header_ul_absent ? false : true; + drb_ngran.sdap_config.sDAP_Header_DL = session->sdap_config.header_dl_absent ? false : true; drb_ngran.pdcp_config = set_bearer_context_pdcp_config(rrc_drb->pdcp_config, um_on_default_drb, redcap_cap); diff --git a/tests/nr-cuup/nr-cuup-load-test.c b/tests/nr-cuup/nr-cuup-load-test.c index 0774b26b1b..047d316afc 100644 --- a/tests/nr-cuup/nr-cuup-load-test.c +++ b/tests/nr-cuup/nr-cuup-load-test.c @@ -418,7 +418,9 @@ static void *sender_thread(void *v) { struct thr_data *d = v; - size_t oh = d->is_ul ? 3 : 0; + // Overhead: PDCP header (2 bytes) for UL packets + // SDAP headers are disabled, so only PDCP header is added + size_t oh = d->is_ul ? 2 : 0; size_t packet_len = d->data_len + oh; uint8_t buf[packet_len]; memset(buf, 0, packet_len); From 7982374f6a4428b32899da151af8f91cc2360dcd Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 27 Aug 2025 12:00:35 +0200 Subject: [PATCH 07/12] RRC/NGAP: add utility function to fill E1 QoS to setup list from NGAP Introduce fill_e1_qos_flow_to_setup to translate pdusession_level_qos_parameter_t (NGAP) into qos_flow_to_setup_t (E1), including ARP and QoS characteristics (dynamic/non-dynamic). Refactor fill_e1_drb_to_setup to use the new helper for building QoS flows. * Only include QoS flows whose drb_id matches the target DRB. * Add DevAssert(drb_ngran.numQosFlow2Setup > 0) to ensure at least one QoS flow is added. This change centralizes and simplifies QoS mapping logic between NGAP inputs and E1 DRB configuration. It ensures correctness by associating QoS flows to the appropriate DRB. Major changes: - Extract QoS flow mapping logic into fill_e1_qos_flow_to_setup helper function - Filter QoS flows by matching drb_id before adding to DRB setup list - Replace inline mapping code with helper function call - Add assertion to verify at least one QoS flow is added per DRB --- openair2/RRC/NR/rrc_gNB_NGAP.c | 48 +++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index ce87d56661..b18964ebe5 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -289,6 +289,31 @@ void rrc_gNB_send_NGAP_NAS_FIRST_REQ(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, NR_RRC itti_send_msg_to_task(TASK_NGAP, rrc->module_id, message_p); } +/// @brief set QoS Flows to Setup in E1 DRB To Setup List +static qos_flow_to_setup_t fill_e1_qos_flow_to_setup(const pdusession_level_qos_parameter_t *qos) +{ + qos_flow_to_setup_t qos_flow = { .qfi = qos->qfi }; + // ARP + ngran_allocation_retention_priority_t *arp_out = &qos_flow.qos_params.alloc_reten_priority; + const qos_arp_t *arp_in = &qos->arp; + arp_out->priority_level = arp_in->priority_level; + arp_out->preemption_capability = arp_in->pre_emp_capability; + arp_out->preemption_vulnerability = arp_in->pre_emp_vulnerability; + // QoS Characteristics + qos_characteristics_t *qos_characteristics = &qos_flow.qos_params.qos_characteristics; + if (qos->fiveQI_type == NON_DYNAMIC) { + qos_characteristics->non_dynamic.fiveqi = qos->fiveQI; + qos_characteristics->non_dynamic.qos_priority_level = qos->qos_priority; + } else { + qos_characteristics->dynamic.fiveqi = qos->fiveQI; + qos_characteristics->dynamic.qos_priority_level = qos->qos_priority; + // NOTE: missing packet error rate and delay budget + } + qos_characteristics->qos_type = qos->fiveQI_type; + + return qos_flow; +} + /** @brief Returns an instance of E1AP DRB To Setup List */ static DRB_nGRAN_to_setup_t fill_e1_drb_to_setup(const drb_t *rrc_drb, const pdusession_t *session, @@ -310,26 +335,13 @@ static DRB_nGRAN_to_setup_t fill_e1_drb_to_setup(const drb_t *rrc_drb, } FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, qos, &session->qos) { - pdusession_level_qos_parameter_t *qos_session = &qos->qos; + if (qos->drb_id != drb_ngran.id) + continue; + const pdusession_level_qos_parameter_t *qos_session = &qos->qos; DevAssert(drb_ngran.numQosFlow2Setup < MAX_QOS_FLOWS); - qos_flow_to_setup_t *qos_flow = &drb_ngran.qosFlows[drb_ngran.numQosFlow2Setup++]; - qos_characteristics_t *qos_char = &qos_flow->qos_params.qos_characteristics; - qos_flow->qfi = qos_session->qfi; - qos_char->qos_type = qos_session->fiveQI_type; - if (qos_char->qos_type == DYNAMIC) { - qos_char->dynamic.fiveqi = qos_session->fiveQI; - qos_char->dynamic.qos_priority_level = qos_session->qos_priority; - } else { - qos_char->non_dynamic.fiveqi = qos_session->fiveQI; - qos_char->non_dynamic.qos_priority_level = qos_session->qos_priority; - } - - ngran_allocation_retention_priority_t *arp_out = &qos_flow->qos_params.alloc_reten_priority; - qos_arp_t *arp_in = &qos_session->arp; - arp_out->priority_level = arp_in->priority_level; - arp_out->preemption_capability = arp_in->pre_emp_capability; - arp_out->preemption_vulnerability = arp_in->pre_emp_vulnerability; + drb_ngran.qosFlows[drb_ngran.numQosFlow2Setup++] = fill_e1_qos_flow_to_setup(qos_session); } + DevAssert(drb_ngran.numQosFlow2Setup > 0); return drb_ngran; } From eaf27acb3b844d772203ac9db355b722306e2710 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Thu, 12 Jun 2025 11:04:34 +0200 Subject: [PATCH 08/12] RRC/NGAP: refactor rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP This commit refactors the rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP function to improve code clarity, fix bugs, and ensure compliance with 3GPP TS 38.413 section 9.2.1.6. The main improvement is ensuring the PDU Session Resource Modify Response message is always sent with mandatory IEs, even when there are no PDU sessions to report in the optional lists. Key changes: - Initialize message structure parameters to 0 by leveraging structure initialization, ensuring clean state - Replace local counter variables (pdu_sessions_done, pdu_sessions_failed) with direct access to resp->nb_of_pdusessions and resp->nb_of_pdusessions_failed, eliminating redundant state tracking and improving code clarity - Remove conditional message sending: always send the response message (per 3GPP TS 38.413 9.2.1.6, PDUSessionResourceModifyResponse must always be sent with mandatory IEs: AMF-UE-NGAP-ID and RAN-UE-NGAP-ID, even when optional lists are empty) - Fix duplicate assignment: remove redundant p->pdusession_id assignment (was assigned twice in the PDU_SESSION_STATUS_DONE case) - Improve failed PDU session handling: use direct structure assignment (failed->cause = session->cause) instead of field-by-field copying, making the code more maintainable - Remove failed PDU session upon response - Enhance logging: improve log messages for better debugging and clarity - Add function documentation comment --- openair2/RRC/NR/rrc_gNB_NGAP.c | 47 ++++++++++++---------------------- 1 file changed, 16 insertions(+), 31 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index b18964ebe5..e1c1bc93dc 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -985,25 +985,22 @@ int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t ins return (0); } +/** @brief Send PDU Session Resource Setup Response (9.2.1.6 3GPP TS 38.413) + * Direction: Direction: NG-RAN node → AMF */ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid) { - MessageDef *msg_p = NULL; - uint16_t pdu_sessions_failed = 0; - uint16_t pdu_sessions_done = 0; - if (!seq_arr_size(&UE->pduSessions)) { LOG_W(NR_RRC, "UE %d: No PDU sessions in the list, don't send NG PDU Session Modify Response\n", UE->rrc_ue_id); return -1; } - msg_p = itti_alloc_new_message (TASK_RRC_GNB, rrc->module_id, NGAP_PDUSESSION_MODIFY_RESP); + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, rrc->module_id, NGAP_PDUSESSION_MODIFY_RESP); if (msg_p == NULL) { LOG_E(NR_RRC, "itti_alloc_new_message failed, msg_p is NULL \n"); return (-1); } - ngap_pdusession_modify_resp_t *resp = &NGAP_PDUSESSION_MODIFY_RESP(msg_p); - LOG_I(NR_RRC, "send message NGAP_PDUSESSION_MODIFY_RESP \n"); + ngap_pdusession_modify_resp_t *resp = &NGAP_PDUSESSION_MODIFY_RESP(msg_p); resp->gNB_ue_ngap_id = UE->rrc_ue_id; resp->amf_ue_ngap_id = UE->amf_ue_ngap_id; @@ -1018,47 +1015,35 @@ int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE continue; } if (session->status == PDU_SESSION_STATUS_DONE) { - LOG_I(NR_RRC, "Successfully modified PDU Session %d \n", session->param.pdusession_id); + LOG_I(NR_RRC, "PDU Session Modify successful (pdusession_id=%d) \n", session->param.pdusession_id); // Update status session->status = PDU_SESSION_STATUS_ESTABLISHED; session->cause.type = NGAP_CAUSE_NOTHING; // Fill response - DevAssert(pdu_sessions_done < NGAP_MAX_PDU_SESSION); - pdusession_modify_t *p = &resp->pdusessions[pdu_sessions_done++]; + DevAssert(resp->nb_of_pdusessions <= NGAP_MAX_PDU_SESSION); + pdusession_modify_t *p = &resp->pdusessions[resp->nb_of_pdusessions++]; p->pdusession_id = session->param.pdusession_id; FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, qos_session, &session->param.qos) { DevAssert(p->nb_of_qos_flow < MAX_QOS_FLOWS); qos_flow_addmod_response_item_t *q = &p->qos[p->nb_of_qos_flow++]; q->qfi = qos_session->qos.qfi; } - LOG_I(NR_RRC, - "Modify Resp (msg index %d, status %d, xid %d): nb_of_pduSessions %ld, pdusession_id %d \n ", - pdu_sessions_done, - session->status, - xid, - seq_arr_size(&UE->pduSessions), - p->pdusession_id); + p->pdusession_id = session->param.pdusession_id; } else if ((session->status == PDU_SESSION_STATUS_NEW) || (session->status == PDU_SESSION_STATUS_ESTABLISHED)) { LOG_D(NR_RRC, "PDU SESSION is NEW or already ESTABLISHED\n"); } else if (session->status == PDU_SESSION_STATUS_FAILED) { - DevAssert(pdu_sessions_failed < NGAP_MAX_PDU_SESSION); - resp->pdusessions_failed[pdu_sessions_failed].pdusession_id = session->param.pdusession_id; - resp->pdusessions_failed[pdu_sessions_failed].cause.type = session->cause.type; - resp->pdusessions_failed[pdu_sessions_failed].cause.value = session->cause.value; - pdu_sessions_failed++; + DevAssert(resp->nb_of_pdusessions_failed <= NGAP_MAX_PDU_SESSION); + pdusession_failed_t *failed = &resp->pdusessions_failed[resp->nb_of_pdusessions_failed++]; + failed->pdusession_id = session->param.pdusession_id; + failed->cause = session->cause; + rm_pduSession(&UE->pduSessions, &UE->drbs, session->param.pdusession_id); } else LOG_W(NR_RRC, "Modify pdu session %d, unknown state %d \n ", session->param.pdusession_id, session->status); } - resp->nb_of_pdusessions = pdu_sessions_done; - resp->nb_of_pdusessions_failed = pdu_sessions_failed; - - if (pdu_sessions_done > 0 || pdu_sessions_failed > 0) { - LOG_D(NR_RRC, "NGAP_PDUSESSION_MODIFY_RESP: sending the message (total pdu session %ld)\n", seq_arr_size(&UE->pduSessions)); - itti_send_msg_to_task (TASK_NGAP, rrc->module_id, msg_p); - } else { - itti_free (ITTI_MSG_ORIGIN_ID(msg_p), msg_p); - } + // Send message to NGAP (always send: if no PDU sessions, only mandatory IEs) + LOG_D(NR_RRC, "Send NG PDU Session Modify Response (nb_of_pdusessions %d, xid %d)\n", resp->nb_of_pdusessions, xid); + itti_send_msg_to_task(TASK_NGAP, rrc->module_id, msg_p); return 0; } From 2ed28c84b35bccd882904aee628d24f78c86d92f Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 23 Apr 2025 01:22:55 +0200 Subject: [PATCH 09/12] RRC/NGAP: refactor rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ TS 38.413 8.2.3 PDU Session Modify: > For each PDU session which failed to be modified, the PDU Session Resource Modify > Unsuccessful Transfer IE shall be included containing the failure cause > If the NG-RAN node receives a PDU SESSION RESOURCE MODIFY REQUEST message containing > some PDU Session ID IEs (in the PDU Session Resource Modify Request List IE) that the > NG-RAN node does not recognize, the NG-RAN node shall report the corresponding invalid > PDU sessions as failed in the PDU SESSION RESOURCE MODIFY RESPONSE message > with an appropriate cause value Changes in the commit: * add rrc_gNB_ngap_pdusession_mod_failure helper function to handle abnormal conditions and send NGAP response with (all) failed PDU sessions * improve error handling: send NGAP response instead of silently returning -1 when UE context lookup fails (unknown RAN UE NGAP ID) * add AMF UE NGAP ID validation: check stored AMF UE NGAP ID matches request and send failure response if mismatch (per 3GPP TS 38.413) * handle unrecognized PDU Session IDs per 3GPP TS 38.413 8.2.3.4: add to failed list to be sent in the reponse message * simplify failure path: reuse rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP instead of manually constructing response message * remove failed PDU sessions from RRC list in rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP * remove redundant code and improve code organization * add bounds checking * improve function signature --- openair2/RRC/NR/rrc_gNB.c | 2 +- openair2/RRC/NR/rrc_gNB_NGAP.c | 75 +++++++++++++++++++++------------- openair2/RRC/NR/rrc_gNB_NGAP.h | 2 +- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 2434cabba3..960f076e1d 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -3262,7 +3262,7 @@ void *rrc_gnb_task(void *args_p) { break; case NGAP_PDUSESSION_MODIFY_REQ: - rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(msg_p, instance); + rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(&NGAP_PDUSESSION_MODIFY_REQ(msg_p), instance); break; case NGAP_PDUSESSION_RELEASE_COMMAND: diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index e1c1bc93dc..7307216877 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -923,33 +923,64 @@ static void nr_rrc_update_pdusession(pdusession_t *dst, const pdusession_resourc } //------------------------------------------------------------------------------ -int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t instance) -//------------------------------------------------------------------------------ +/** @brief Handle abnormal conditions in PDU Session Modify procedure. + * Send a response with failed of PDU sessions only, for all PDU Sessions in the request */ +static int rrc_gNB_ngap_pdusession_mod_failure(int module_id, const ngap_pdusession_modify_req_t *req, const ngap_cause_t cause) { - rrc_gNB_ue_context_t *ue_context_p = NULL; - - ngap_pdusession_modify_req_t *req = &NGAP_PDUSESSION_MODIFY_REQ(msg_p); + MessageDef *msg_p = itti_alloc_new_message (TASK_RRC_GNB, module_id, NGAP_PDUSESSION_MODIFY_RESP); + if (msg_p == NULL) { + LOG_E(NR_RRC, "itti_alloc_new_message failed, msg_p is NULL \n"); + return -1; + } + ngap_pdusession_modify_resp_t *resp = &NGAP_PDUSESSION_MODIFY_RESP(msg_p); + resp->amf_ue_ngap_id = req->amf_ue_ngap_id; + resp->gNB_ue_ngap_id = req->gNB_ue_ngap_id; + resp->nb_of_pdusessions_failed = req->nb_pdusessions_tomodify; + resp->pdusessions_failed->cause = cause; + for (int i = 0; i < req->nb_pdusessions_tomodify; i++) + resp->pdusessions_failed[i].pdusession_id = req->pdusession[i].pdusession_id; + itti_send_msg_to_task(TASK_NGAP, module_id, msg_p); + return 0; +} +int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(const ngap_pdusession_modify_req_t *req, instance_t instance) +{ gNB_RRC_INST *rrc = RC.nrrrc[instance]; - ue_context_p = rrc_gNB_get_ue_context(rrc, req->gNB_ue_ngap_id); + + rrc_gNB_ue_context_t *ue_context_p = rrc_gNB_get_ue_context(rrc, req->gNB_ue_ngap_id); if (ue_context_p == NULL) { LOG_W(NR_RRC, "[gNB %ld] In NGAP_PDUSESSION_MODIFY_REQ: unknown UE from NGAP ids (%u)\n", instance, req->gNB_ue_ngap_id); - // TO implement return setup failed + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_INCONSISTENT_REMOTE_UE_NGAP_ID}; + rrc_gNB_ngap_pdusession_mod_failure(rrc->module_id, req, cause); return (-1); } gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + if (UE->amf_ue_ngap_id != req->amf_ue_ngap_id) { + LOG_W(NR_RRC, + "Stored amf_ue_ngap_id %ld for UE %x does not match the requested one %ld\n", + UE->amf_ue_ngap_id, + UE->rrc_ue_id, + req->amf_ue_ngap_id); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_INCONSISTENT_REMOTE_UE_NGAP_ID}; + rrc_gNB_ngap_pdusession_mod_failure(rrc->module_id, req, cause); + return -1; + } + + uint8_t xid = rrc_gNB_get_next_transaction_identifier(rrc->module_id); bool all_failed = true; + DevAssert(req->nb_pdusessions_tomodify <= NGAP_MAX_PDU_SESSION); for (int i = 0; i < req->nb_pdusessions_tomodify; i++) { const pdusession_resource_item_t *sessMod = &req->pdusession[i]; rrc_pdu_session_param_t *session = find_pduSession(&UE->pduSessions, sessMod->pdusession_id); if (!session) { + /* 8.2.3.4 3GPP TS 38.413: If the NG-RAN node receives unrecognized PDU Session ID IEs, + the NG-RAN node shall report the corresponding invalid PDU sessions as failed. So we add it to the list. */ LOG_W(NR_RRC, "Requested modification of non-existing PDU session, refusing modification\n"); - rrc_pdu_session_param_t to_add = {0}; - to_add.status = PDU_SESSION_STATUS_FAILED; - to_add.param.pdusession_id = sessMod->pdusession_id; - to_add.cause.type = NGAP_CAUSE_RADIO_NETWORK; - to_add.cause.value = NGAP_CAUSE_RADIO_NETWORK_UNKNOWN_PDU_SESSION_ID; - seq_arr_push_back(&UE->pduSessions, &to_add, sizeof(to_add)); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_UNKNOWN_PDU_SESSION_ID}; + pdusession_t session = {.pdusession_id = sessMod->pdusession_id}; + rrc_pdu_session_param_t *added = add_pduSession(&UE->pduSessions, &session); + added->status = PDU_SESSION_STATUS_FAILED; + added->cause = cause; } else { all_failed = false; session->status = PDU_SESSION_STATUS_NEW; @@ -961,26 +992,12 @@ int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t ins if (!all_failed) { rrc_gNB_modify_dedicatedRRCReconfiguration(rrc, UE); } else { - LOG_I(NR_RRC, - "pdu session modify failed, fill NGAP_PDUSESSION_MODIFY_RESP with the pdu session information that failed to modify \n"); MessageDef *msg_fail_p = itti_alloc_new_message(TASK_RRC_GNB, 0, NGAP_PDUSESSION_MODIFY_RESP); if (msg_fail_p == NULL) { LOG_E(NR_RRC, "itti_alloc_new_message failed, msg_fail_p is NULL \n"); - return (-1); + return -1; } - ngap_pdusession_modify_resp_t *msg = &NGAP_PDUSESSION_MODIFY_RESP(msg_fail_p); - msg->gNB_ue_ngap_id = req->gNB_ue_ngap_id; - msg->nb_of_pdusessions = 0; - - FOR_EACH_SEQ_ARR(rrc_pdu_session_param_t *, session, &UE->pduSessions) { - if (session->status == PDU_SESSION_STATUS_FAILED) { - msg->pdusessions_failed[msg->nb_of_pdusessions_failed].pdusession_id = session->param.pdusession_id; - msg->pdusessions_failed[msg->nb_of_pdusessions_failed].cause.type = session->cause.type; - msg->pdusessions_failed[msg->nb_of_pdusessions_failed].cause.value = session->cause.value; - msg->nb_of_pdusessions_failed++; - } - } - itti_send_msg_to_task(TASK_NGAP, instance, msg_fail_p); + rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(rrc, UE, xid); } return (0); } diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.h b/openair2/RRC/NR/rrc_gNB_NGAP.h index c3f692ff5f..2530b3a803 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.h +++ b/openair2/RRC/NR/rrc_gNB_NGAP.h @@ -60,7 +60,7 @@ void rrc_gNB_send_NGAP_PDUSESSION_SETUP_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE void rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ(MessageDef *msg_p, instance_t instance); -int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(MessageDef *msg_p, instance_t instance); +int rrc_gNB_process_NGAP_PDUSESSION_MODIFY_REQ(const ngap_pdusession_modify_req_t *req, instance_t instance); int rrc_gNB_send_NGAP_PDUSESSION_MODIFY_RESP(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t xid); From 5ad1f14cb816baa9f86b0496fb0f32e54956b379 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Tue, 9 Dec 2025 21:52:25 +0100 Subject: [PATCH 10/12] RRC: refactor F1 DRB setup list filling into shared helpers Extract common code from `fill_drb_to_be_setup_from_e1_resp` and `rrc_fill_f1_drb_to_setup` into reusable helper functions to reduce duplication and improve maintainability. This refactoring enables multi-QoS flow handling per DRB and prepares helpers for upcoming PDU Session Modify changes. Impact: - Enables multi-QoS flow support: `rrc_fill_f1_drb_to_setup` now collects all QFIs mapped to a DRB instead of only the first one - Reduces code duplication between E1 response and direct DRB setup code paths - Improves maintainability by centralizing F1 DRB setup logic Major changes: - Add `fill_f1_drb_info_nr`: fills DRB information IE including QoS flows from QFI list - Add `fill_f1_drb_to_be_setup`: creates F1 DRB to setup structure from RRC DRB, PDU session, and QFI list - Add `fill_f1_drbs_from_e1`: processes DRBs from E1 response and extracts QFIs from drb_config->qosFlows - Refactor `fill_drb_to_be_setup_from_e1_resp` to use `fill_f1_drbs_from_e1` - Refactor `rrc_fill_f1_drb_to_setup` to collect all QFIs mapped to each DRB and use `fill_f1_drb_to_be_setup` --- openair2/RRC/NR/nr_rrc_proto.h | 1 + openair2/RRC/NR/rrc_gNB.c | 190 ++++++++++++++++++++------------- 2 files changed, 115 insertions(+), 76 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_proto.h b/openair2/RRC/NR/nr_rrc_proto.h index 1db6a4d236..28fffd2cda 100644 --- a/openair2/RRC/NR/nr_rrc_proto.h +++ b/openair2/RRC/NR/nr_rrc_proto.h @@ -39,6 +39,7 @@ #include "NR_CG-ConfigInfo.h" #include "NR_RRCReconfiguration.h" #include "RRC/NR/MESSAGES/asn1_msg.h" +#include "f1ap_messages_types.h" #define SRB1 1 #define SRB2 2 diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index 960f076e1d..db83bb67d2 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2852,6 +2852,104 @@ f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qo return qos_char; } +static f1ap_drb_info_nr_t fill_f1_drb_info_nr(pdusession_t *pdu, const uint8_t *qfis, const int num_qfis) +{ + f1ap_drb_info_nr_t drb_info = {0}; + drb_info.nssai = pdu->nssai; + drb_info.flows_len = num_qfis; + drb_info.flows = calloc_or_fail(num_qfis, sizeof(*drb_info.flows)); + for (int i = 0; i < num_qfis; i++) { + int qfi = qfis[i]; + /* find the stored QoS flow parameters for this QFI */ + nr_rrc_qos_t *qos_param = find_qos(&pdu->qos, qfi); + DevAssert(qos_param); + /* QoS flows Flows Mapped to DRB Item */ + f1ap_drb_flows_mapped_t *flow = &drb_info.flows[i]; + flow->qfi = qfi; + flow->param = get_qos_char_from_qos_flow_param(&qos_param->qos); + } + /* DRB QoS IE: we just reuse the ones from the first flow */ + drb_info.drb_qos = drb_info.flows[0].param; + return drb_info; +} + +/** @brief Fill F1 DRB to Be Setup List (UE Context Setup Request) + * @param rrc RRC instance + * @param rrc_drb RRC DRB structure + * @param pdu PDU session parameters + * @param qfis List of QFIs + * @param num_qfis Number of QFIs in list + * @return Filled F1 DRB to setup structure */ +static f1ap_drb_to_setup_t fill_f1_drb_to_be_setup(const gNB_RRC_INST *rrc, + const drb_t *rrc_drb, + rrc_pdu_session_param_t *pdu, + const uint8_t *qfis, + int num_qfis) +{ + DevAssert(rrc); + DevAssert(rrc_drb); + DevAssert(pdu); + DevAssert(qfis); + DevAssert(num_qfis > 0); + DevAssert(num_qfis <= MAX_QOS_FLOWS); + + f1ap_drb_to_setup_t drb = {0}; + + drb.id = rrc_drb->drb_id; + drb.qos_choice = F1AP_QOS_CHOICE_NR; + /* DRB Info (including QoS flows) */ + drb.nr = fill_f1_drb_info_nr(&pdu->param, qfis, num_qfis); + + /* tunnel info */ + if (rrc_drb->cuup_tunnel_config.addr.length != sizeof(in_addr_t)) { + LOG_W(RRC, + "IPv4 address expected (length == %zu), got length %d\n", + sizeof(in_addr_t), + rrc_drb->cuup_tunnel_config.addr.length); + } + memcpy(&drb.up_ul_tnl[0].tl_address, rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(in_addr_t)); + drb.up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; + drb.up_ul_tnl_len = 1; + + /* RLC mode */ + drb.rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; + + /* PDCP SN length */ + const nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; + DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); + drb.dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb.dl_pdcp_sn_len)); + *drb.dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + drb.ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb.ul_pdcp_sn_len)); + *drb.ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + + return drb; +} + +/** @brief Helper function to process DRBs from a PDU session's DRB setup list */ +static int fill_f1_drbs_from_e1(const gNB_RRC_INST *rrc, + gNB_RRC_UE_t *UE, + rrc_pdu_session_param_t *pdu, + const DRB_nGRAN_setup_t *drb_list, + const int drb_list_len, + f1ap_drb_to_setup_t drbs[32]) +{ + int nb_drb = 0; + for (int i = 0; i < drb_list_len; i++) { + const DRB_nGRAN_setup_t *drb_config = &drb_list[i]; + drb_t *rrc_drb = get_drb(&UE->drbs, drb_config->id); + DevAssert(rrc_drb); + int nb_qos_flows = drb_config->numQosFlowSetup; + DevAssert(nb_qos_flows <= MAX_QOS_FLOWS); + AssertFatal(nb_qos_flows > 0, "must map at least one flow to a DRB\n"); + uint8_t qfis[MAX_QOS_FLOWS] = {0}; + for (int j = 0; j < nb_qos_flows; j++) + qfis[j] = drb_config->qosFlows[j].qfi; + drbs[nb_drb++] = fill_f1_drb_to_be_setup(rrc, rrc_drb, pdu, qfis, nb_qos_flows); + } + DevAssert(nb_drb < MAX_DRBS_PER_UE); + return nb_drb; +} + /* \brief fills a list of DRBs to be setup from a number of PDU sessions in E1 * bearer setup response */ static int fill_drb_to_be_setup_from_e1_resp(const gNB_RRC_INST *rrc, @@ -2862,56 +2960,12 @@ static int fill_drb_to_be_setup_from_e1_resp(const gNB_RRC_INST *rrc, { int nb_drb = 0; for (int p = 0; p < numPduSession; ++p) { - rrc_pdu_session_param_t *pdu = find_pduSession(&UE->pduSessions, pduSession[p].id); + const pdu_session_setup_t *session = &pduSession[p]; + rrc_pdu_session_param_t *pdu = find_pduSession(&UE->pduSessions, session->id); DevAssert(pdu); - for (int i = 0; i < pduSession[p].numDRBSetup; i++) { - const DRB_nGRAN_setup_t *drb_config = &pduSession[p].DRBnGRanList[i]; - drb_t *rrc_drb = get_drb(&UE->drbs, pduSession[p].DRBnGRanList[i].id); - nr_pdcp_configuration_t pdcp = rrc_drb->pdcp_config; - DevAssert(rrc_drb); - - DevAssert(nb_drb < MAX_DRBS_PER_UE); - f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; - drb->id = rrc_drb->drb_id; - - drb->qos_choice = F1AP_QOS_CHOICE_NR; - - /* pass QoS info to MAC */ - int nb_qos_flows = drb_config->numQosFlowSetup; - AssertFatal(nb_qos_flows > 0, "must map at least one flow to a DRB\n"); - DevAssert(nb_qos_flows <= MAX_QOS_FLOWS); - drb->nr.flows_len = nb_qos_flows; - drb->nr.flows = calloc_or_fail(nb_qos_flows, sizeof(*drb->nr.flows)); - for (int j = 0; j < nb_qos_flows; j++) { - int qfi = drb_config->qosFlows[j].qfi; - /* find the QoS characteristics stored at RRC based on QFI returned - * from E1. We expect this to correspond to a QFI we passed to E1 - * previously, so we expect it to exist. */ - nr_rrc_qos_t *qos_param = find_qos(&pdu->param.qos, qfi); - DevAssert(qos_param); - f1ap_drb_flows_mapped_t *flow = &drb->nr.flows[j]; - flow->qfi = qfi; - flow->param = get_qos_char_from_qos_flow_param(&qos_param->qos); - } - /* the DRB QoS parameters: we just reuse the ones from the first flow */ - drb->nr.drb_qos = drb->nr.flows[0].param; - - /* pass NSSAI info to MAC */ - drb->nr.nssai = pdu->param.nssai; - - drb->up_ul_tnl[0].tl_address = drb_config->UpParamList[0].tl_info.tlAddress; - drb->up_ul_tnl[0].teid = drb_config->UpParamList[0].tl_info.teId; - drb->up_ul_tnl_len = 1; - drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; - DevAssert(pdcp.drb.sn_size == 18 || pdcp.drb.sn_size == 12); - drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); - *drb->dl_pdcp_sn_len = pdcp.drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); - *drb->ul_pdcp_sn_len = pdcp.drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - - nb_drb++; - } + nb_drb += fill_f1_drbs_from_e1(rrc, UE, pdu, session->DRBnGRanList, session->numDRBSetup, drbs); } + DevAssert(nb_drb < MAX_DRBS_PER_UE); return nb_drb; } @@ -3555,39 +3609,23 @@ static int rrc_fill_f1_drb_to_setup(const gNB_RRC_INST *rrc, const gNB_RRC_UE_t FOR_EACH_SEQ_ARR(drb_t *, rrc_drb, &ue->drbs) { DevAssert(nb_drb < MAX_DRBS_PER_UE); - f1ap_drb_to_setup_t *drb = &drbs[nb_drb]; - nr_pdcp_configuration_t *pdcp = &rrc_drb->pdcp_config; - nb_drb++; /* fetch an existing PDU session for this DRB */ rrc_pdu_session_param_t *pdu = find_pduSession_from_drbId((gNB_RRC_UE_t *)ue, rrc_drb->drb_id); AssertFatal(pdu != NULL, "no PDU session for DRB ID %d\n", rrc_drb->drb_id); - drb->id = rrc_drb->drb_id; + /* Collect all QFIs mapped to this DRB */ + uint8_t qfis[MAX_QOS_FLOWS] = {0}; + int num_qfis = 0; + FOR_EACH_SEQ_ARR (nr_rrc_qos_t *, qos, &pdu->param.qos) { + if (qos->drb_id == rrc_drb->drb_id) { + DevAssert(num_qfis < MAX_QOS_FLOWS); + qfis[num_qfis++] = qos->qos.qfi; + } + } + AssertFatal(num_qfis > 0, "no QoS flows mapped to DRB ID %d\n", rrc_drb->drb_id); - drb->qos_choice = F1AP_QOS_CHOICE_NR; - drb->nr.nssai = pdu->param.nssai; - drb->nr.flows_len = 1; - drb->nr.flows = calloc_or_fail(1, sizeof(*drb->nr.flows)); - - /* QoS flow associated with this DRB: use first QoS flow */ - AssertFatal(seq_arr_size(&pdu->param.qos) == 1, "only 1 Qos flow supported\n"); - nr_rrc_qos_t *qos_param = (nr_rrc_qos_t *)seq_arr_at(&pdu->param.qos, 0); - DevAssert(qos_param->qos.qfi > 0); - drb->nr.flows[0].qfi = qos_param->qos.qfi; - drb->nr.flows[0].param = get_qos_char_from_qos_flow_param(&qos_param->qos); - /* the DRB QoS parameters: reuse the ones from the first flow */ - drb->nr.drb_qos = drb->nr.flows[0].param; - - memcpy(&drb->up_ul_tnl[0].tl_address, &rrc_drb->cuup_tunnel_config.addr.buffer, sizeof(uint8_t) * 4); - drb->up_ul_tnl[0].teid = rrc_drb->cuup_tunnel_config.teid; - drb->up_ul_tnl_len = 1; - - drb->rlc_mode = rrc->configuration.um_on_default_drb ? F1AP_RLC_MODE_UM_BIDIR : F1AP_RLC_MODE_AM; - DevAssert(pdcp->drb.sn_size == 18 || pdcp->drb.sn_size == 12); - drb->dl_pdcp_sn_len = malloc_or_fail(sizeof(*drb->dl_pdcp_sn_len)); - *drb->dl_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; - drb->ul_pdcp_sn_len = malloc_or_fail(sizeof(*drb->ul_pdcp_sn_len)); - *drb->ul_pdcp_sn_len = pdcp->drb.sn_size == 18 ? F1AP_PDCP_SN_18B : F1AP_PDCP_SN_12B; + /* Use the common function to fill DRB setup */ + drbs[nb_drb++] = fill_f1_drb_to_be_setup(rrc, rrc_drb, pdu, qfis, num_qfis); } return nb_drb; } From 3d41d28f1d6d048746d3a2c443f4bcdaebc553a9 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Fri, 29 Aug 2025 16:47:51 +0200 Subject: [PATCH 11/12] RRC: enhance AMBR management in CU-CP According to TS 38.413: * AMBR is conditional to PDU Session Resource to setup in the Initial UE Context Setup procedure * The UE Aggregate Maximum Bit Rate IE should be sent to the NG-RAN node if the AMF has not sent it previously. If it is included in the PDU SESSION RESOURCE SETUP REQUEST message, the NG-RAN node shall store it in the UE context * Range is INTEGER [0..4,000,000,000,000] To this purpose, changes in the commit: * Init AMBR to out of range value when creating UE context * Store AMBR in UE context whenever is received * Assert if not set at the time of Bearer Setup (should be known at that stage) --- openair2/RRC/NR/nr_rrc_defs.h | 8 ++++++++ openair2/RRC/NR/rrc_gNB.c | 4 ++-- openair2/RRC/NR/rrc_gNB_NGAP.c | 24 +++++++++++++++++++----- openair2/RRC/NR/rrc_gNB_UE_context.c | 2 ++ 4 files changed, 31 insertions(+), 7 deletions(-) diff --git a/openair2/RRC/NR/nr_rrc_defs.h b/openair2/RRC/NR/nr_rrc_defs.h index 46e1ef3384..08e0d27888 100644 --- a/openair2/RRC/NR/nr_rrc_defs.h +++ b/openair2/RRC/NR/nr_rrc_defs.h @@ -166,6 +166,11 @@ typedef struct { pdusession_level_qos_parameter_t qos; } nr_rrc_qos_t; +typedef struct { + uint64_t dl_br; + uint64_t ul_br; +} nr_rrc_ambr_t; + /* forward declaration */ typedef struct nr_handover_context_s nr_handover_context_t; @@ -244,6 +249,9 @@ typedef struct gNB_RRC_UE_s { int n_initial_pdu; pdusession_t *initial_pdus; + // Aggregate Maximum Bit Rate + nr_rrc_ambr_t ambr; + /* Nas Pdu */ byte_array_t nas_pdu; diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index db83bb67d2..39c1d31490 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -1963,7 +1963,7 @@ static void handle_ueCapabilityInformation(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, * up security and request capabilities, so trigger PDU sessions now. The * UE NAS message will be forwarded in the corresponding reconfiguration, * the Initial context setup response after reconfiguration complete. */ - if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, 0)) { + if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, UE->ambr.dl_br)) { LOG_W(NR_RRC, "Failed to setup bearers for UE %d: send Initial Context Setup Response\n", UE->rrc_ue_id); rrc_gNB_send_NGAP_INITIAL_CONTEXT_SETUP_RESP(rrc, UE); rrc_forward_ue_nas_message(rrc, UE); @@ -2213,7 +2213,7 @@ static int rrc_gNB_decode_dcch(gNB_RRC_INST *rrc, const f1ap_ul_rrc_message_t *m * to set up security, so trigger PDU sessions now. The UE NAS * message will be forwarded in the corresponding reconfiguration, * the Initial context setup response after reconfiguration complete. */ - if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, 0)) { + if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, UE->ambr.dl_br)) { LOG_W(NR_RRC, "Failed to setup bearers for UE %d: send Initial Context Setup Response\n", UE->rrc_ue_id); rrc_gNB_send_NGAP_INITIAL_CONTEXT_SETUP_RESP(rrc, UE); rrc_forward_ue_nas_message(rrc, UE); diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 7307216877..f91e346f02 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -367,6 +367,10 @@ static nr_sdap_configuration_t get_sdap_config(const bool enable_sdap) */ bool trigger_bearer_setup(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, pdusession_t *sessions, uint64_t ueAggMaxBitRateDownlink) { + if (ueAggMaxBitRateDownlink == UINT64_MAX) { + LOG_E(NR_RRC, "UE %d: UE aggregate maximum bitrate must be known by the NG-RAN node\n", UE->rrc_ue_id); + return false; + } AssertFatal(UE->as_security_active, "logic bug: security should be active when activating DRBs\n"); e1ap_bearer_setup_req_t bearer_req = {0}; @@ -497,12 +501,15 @@ int rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ(MessageDef *msg_p, instance_t UE->nas_pdu = req->nas_pdu; if (req->nb_of_pdusessions > 0) { + AssertFatal(req->has_ue_ambr, "UE aggregate maximum bitrate is required when there are PDU sessions to setup"); /* if there are PDU sessions to setup, store them to be created once * security (and UE capabilities) are received */ UE->n_initial_pdu = req->nb_of_pdusessions; UE->initial_pdus = calloc_or_fail(UE->n_initial_pdu, sizeof(*UE->initial_pdus)); for (int i = 0; i < UE->n_initial_pdu; ++i) cp_pdusession_resource_item_to_pdusession(&UE->initial_pdus[i], &req->pdusession[i]); + UE->ambr.dl_br = req->ue_ambr.br_dl; + UE->ambr.ul_br = req->ue_ambr.br_ul; } /* security */ @@ -525,7 +532,7 @@ int rrc_gNB_process_NGAP_INITIAL_CONTEXT_SETUP_REQ(MessageDef *msg_p, instance_t // do not remove the above allocation which is reused here: this is used // in handle_rrcReconfigurationComplete() to know that we need to send a // Initial context setup response message - if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, 0)) { + if (!trigger_bearer_setup(rrc, UE, UE->n_initial_pdu, UE->initial_pdus, UE->ambr.dl_br)) { LOG_W(NR_RRC, "UE %d: reject PDU Session Setup in Initial Context Setup Response\n", UE->rrc_ue_id); ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_RESOURCES_NOT_AVAILABLE_FOR_THE_SLICE}; send_ngap_initial_context_setup_resp_fail(rrc->module_id, req, cause); @@ -878,9 +885,13 @@ void rrc_gNB_process_NGAP_PDUSESSION_SETUP_REQ(MessageDef *msg_p, instance_t ins for (int i = 0; i < msg->nb_pdusessions_tosetup; ++i) cp_pdusession_resource_item_to_pdusession(&to_setup[i], &msg->pdusession[i]); - uint64_t dl_ambr = msg->has_ue_ambr ? msg->ueAggMaxBitRate.br_dl : 0; + /** Set AMBR if provided (optional) */ + if (msg->has_ue_ambr) { + UE->ambr.dl_br = msg->ueAggMaxBitRate.br_dl; + UE->ambr.ul_br = msg->ueAggMaxBitRate.br_ul; + } - if (!trigger_bearer_setup(rrc, UE, msg->nb_pdusessions_tosetup, to_setup, dl_ambr)) { + if (!trigger_bearer_setup(rrc, UE, msg->nb_pdusessions_tosetup, to_setup, UE->ambr.dl_br)) { // Reject PDU Session Resource setup if there's no CU-UP associated LOG_W(NR_RRC, "UE %d: reject PDU Session Setup in PDU Session Resource Setup Response\n", UE->rrc_ue_id); ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_RESOURCES_NOT_AVAILABLE_FOR_THE_SLICE}; @@ -1199,7 +1210,11 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga cp_pdusession_transfer_to_pdusession(pdu, &ho_pdu->pdusessionTransfer); } - if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, to_setup, msg->ue_ambr.br_dl)) { + // Store UE aggregate maximum bitrate + UE->ambr.dl_br = msg->ue_ambr.br_dl; + UE->ambr.ul_br = msg->ue_ambr.br_ul; + + if (!trigger_bearer_setup(rrc, UE, msg->nb_of_pdusessions, to_setup, UE->ambr.dl_br)) { LOG_E(NR_RRC, "Failed to establish PDU session: handover failed\n"); ngap_handover_failure_t fail = { @@ -1211,7 +1226,6 @@ int rrc_gNB_process_Handover_Request(gNB_RRC_INST *rrc, instance_t instance, nga rrc_remove_ue(rrc, ue_context_p); return -1; } - return 0; } diff --git a/openair2/RRC/NR/rrc_gNB_UE_context.c b/openair2/RRC/NR/rrc_gNB_UE_context.c index 86838927ab..c05b8a33a8 100644 --- a/openair2/RRC/NR/rrc_gNB_UE_context.c +++ b/openair2/RRC/NR/rrc_gNB_UE_context.c @@ -218,6 +218,8 @@ rrc_gNB_ue_context_t *rrc_gNB_create_ue_context(sctp_assoc_t assoc_id, ue->rrc_ue_id); bool success = cu_add_f1_ue_data(ue->rrc_ue_id, &ue_data); DevAssert(success); + ue->ambr.dl_br = UINT64_MAX; + ue->ambr.ul_br = UINT64_MAX; // Initialise setup PDU Sessions list seq_arr_init(&ue->pduSessions, sizeof(rrc_pdu_session_param_t)); From d1150d90e139f4e4fcce9bbc5353eaf009b17075 Mon Sep 17 00:00:00 2001 From: Guido Casati Date: Wed, 21 Jan 2026 12:15:49 +0100 Subject: [PATCH 12/12] NR RRC/SDAP: enable SDAP by default and update CI/docs - Change gNB RRC enable_sdap default to true - Update RRC and RUNMODEM docs to reflect new default behavior - Clean up CI gNB configs by removing redundant enable_sdap=1 - Keep explicit SDAP disable config for NFAPI gNB VNF --- ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf | 2 +- .../conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf | 2 +- doc/RRC/rrc-usage.md | 2 +- doc/RUNMODEM.md | 2 +- openair2/GNB_APP/gnb_paramdef.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf b/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf index bae2bc15ff..fc957d9eef 100644 --- a/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf +++ b/ci-scripts/conf_files/gnb.sa.band66.106prb.rfsim.conf @@ -18,7 +18,7 @@ gNBs = ////////// Physical parameters: min_rxtxtime = 6; - enable_sdap = 1; + enable_sdap = 0; servingCellConfigCommon = ( { diff --git a/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf b/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf index 6715202229..22a65995d5 100644 --- a/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf +++ b/ci-scripts/conf_files/untested/gnb.sa.band66.fr1.106PRB.usrpn300.conf @@ -18,7 +18,7 @@ gNBs = ////////// Physical parameters: min_rxtxtime = 6; - enable_sdap = 1; + enable_sdap = 0; servingCellConfigCommon = ( { diff --git a/doc/RRC/rrc-usage.md b/doc/RRC/rrc-usage.md index 05f55d5123..baeee1480d 100644 --- a/doc/RRC/rrc-usage.md +++ b/doc/RRC/rrc-usage.md @@ -84,7 +84,7 @@ the [MAC configuration](../MAC/mac-usage.md) as well for SIB configuration. `0xffffff` is a reserved value and means "no SD" Note that: SST=1, no SD is "eMBB"; SST=2, no SD is "URLLC"; SST=3, no SD is "mMTC" -- `enable_sdap` (default: false): enable the use of the SDAP layer. If +- `enable_sdap` (default: true): enable the use of the SDAP layer. If 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. diff --git a/doc/RUNMODEM.md b/doc/RUNMODEM.md index 1515172b24..b747775860 100644 --- a/doc/RUNMODEM.md +++ b/doc/RUNMODEM.md @@ -544,7 +544,7 @@ sudo ./nr-uesoftmodem --do-ra #### Run OAI with SDAP & Custom DRBs -To run OAI gNB with SDAP, simply include `--gNBs.[0].enable_sdap 1` to the binary's arguments. +SDAP is enabled by default. To disable SDAP, include `--gNBs.[0].enable_sdap 0` to the binary's arguments. The DRB creation is dependent on the 5QI. If the 5QI corresponds to a GBR Flow it assigns a dedicated data radio bearer. diff --git a/openair2/GNB_APP/gnb_paramdef.h b/openair2/GNB_APP/gnb_paramdef.h index 09a19b2d33..8b140c5aac 100644 --- a/openair2/GNB_APP/gnb_paramdef.h +++ b/openair2/GNB_APP/gnb_paramdef.h @@ -170,7 +170,7 @@ typedef enum { {GNB_CONFIG_STRING_ULPRBBLACKLIST, NULL, 0, .strptr=NULL,.defstrval="", TYPE_STRING, 0}, \ {GNB_CONFIG_STRING_UMONDEFAULTDRB, NULL, PARAMFLAG_BOOL, .uptr=NULL, .defuintval=0, TYPE_UINT, 0}, \ {GNB_CONFIG_STRING_FORCE256QAMOFF, GNB_CONFIG_HLP_FORCE256QAMOFF, PARAMFLAG_BOOL, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \ -{GNB_CONFIG_STRING_ENABLE_SDAP, GNB_CONFIG_HLP_STRING_ENABLE_SDAP, PARAMFLAG_BOOL,.iptr=NULL, .defintval=0, TYPE_INT, 0}, \ +{GNB_CONFIG_STRING_ENABLE_SDAP, GNB_CONFIG_HLP_STRING_ENABLE_SDAP, PARAMFLAG_BOOL,.iptr=NULL, .defintval=1, TYPE_INT, 0}, \ {GNB_CONFIG_STRING_GNB_DU_ID, GNB_CONFIG_HLP_GNB_DU_ID, 0, .u64ptr=NULL, .defint64val=1, TYPE_UINT64, 0}, \ {GNB_CONFIG_STRING_GNB_CU_UP_ID, GNB_CONFIG_HLP_GNB_CU_UP_ID, 0, .u64ptr=NULL, .defint64val=1, TYPE_UINT64, 0}, \ {GNB_CONFIG_STRING_USE_DELTA_MCS, GNB_CONFIG_HLP_USE_DELTA_MCS, 0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \