Merge remote-tracking branch 'GuidoCasati/sdap-header-fix' into integration_2026_w28

SDAP header handling fixes (#249)

OAI SDAP treated SDAP header presence as a single PDU-session flag
(enable_sdap), so a valid asymmetric RRC config (UL header present, DL
header absent) caused the UE to parse downlink payload as an SDAP QFI
and drop traffic (e.g. IPv4 0x45 -> bogus QFI 5).

SDAP now follows RRC SDAP-Config per DRB and per direction from (TS
38.331 / TS 37.324). Header presence, QFI-to-DRB mapping, and
default-DRB fallback all derive from what RRC configured for each DRB.

In the new implementation, RRC (SDAP-Config IE) fills sdap_config_t per
DRB (role, defaultDRB, mapped QFIs), SDAP entity stores it in
qfi2drb_table + default_drb row to be used in the TX/RX data path.

Also, simplify mapping logic, improves E1 QoS-flow remap, and completes
UE reflective QoS mapping with the correct entity_role on new QFI rows.

Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
Reviewed-by: Bartosz Podrygajlo <bartosz.podrygajlo@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-07-09 19:45:58 +02:00
9 changed files with 169 additions and 178 deletions

View File

@@ -90,9 +90,9 @@ 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: 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.
- `enable_sdap` (default: true): set `sdap-HeaderUL` and `sdap-HeaderDL` to
present in the RRC `SDAP-Config` IE for SA PDU sessions. If false, both
headers are absent (per DRB). SDAP entities are still created, SDAP layer always enabled.
- `cu_sibs` (default: `[]`) list of SIBs to give to the DU for transmission.
Currently supported:
- SIB2: serving-cell reselection parameters (configured in `sib2_config`)

View File

@@ -2443,7 +2443,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");
LOG_I(GNB_APP, "SDAP UL/DL headers in RRC are %s\n", nrrrc_config.enable_sdap ? "present" : "absent");
nrrrc_config.um_on_default_drb = *(GNBParamList.paramarray[i][GNB_UMONDEFAULTDRB_IDX].uptr);
}//

View File

@@ -102,7 +102,7 @@ typedef enum {
#define GNB_CONFIG_STRING_1ST_ACTIVE_BWP "first_active_bwp"
#define GNB_CONFIG_STRING_LIMIT_RSRP_REPORT "max_num_RSRP_reported"
#define GNB_CONFIG_HLP_STRING_ENABLE_SDAP "enable the SDAP layer\n"
#define GNB_CONFIG_HLP_STRING_ENABLE_SDAP "set sdap-HeaderUL/DL present in RRC SDAP-Config (false = both absent)\n"
#define GNB_CONFIG_HLP_FORCE256QAMOFF "suppress activation of 256 QAM despite UE support"
#define GNB_CONFIG_HLP_MAXMIMOLAYERS "limit on maxMIMO-layers for DL"
#define GNB_CONFIG_HLP_DISABLE_HARQ "disable feedback for all HARQ processes (REL17 feature)"

View File

@@ -427,9 +427,15 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req)
DevAssert(to_modif->numQosFlowsMod <= E1AP_MAX_NUM_QOS_FLOWS);
modified->numQosFlowSetup = to_modif->numQosFlowsMod;
uint8_t qfi_list[E1AP_MAX_NUM_QOS_FLOWS];
sdap_config_t sdap = {
.pdusession_id = req_pdu_mod->sessionId,
.drb_id = to_modif->id,
.mappedQFIs2AddCount = to_modif->numQosFlowsMod,
};
for (int q = 0; q < to_modif->numQosFlowsMod; q++) {
modified->qosFlows[q].qfi = qfi_list[q] = to_modif->qosFlows[q].qfi;
sdap.mappedQFIs2Add[q] = to_modif->qosFlows[q].qfi;
modified->qosFlows[q].qfi = to_modif->qosFlows[q].qfi;
}
LOG_D(NR_RRC,
@@ -438,11 +444,7 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req)
req_pdu_mod->sessionId,
to_modif->id,
to_modif->numQosFlowsMod);
nr_sdap_entity_update_qos_flows(req->gNB_cu_up_ue_id,
req_pdu_mod->sessionId,
to_modif->id,
qfi_list,
to_modif->numQosFlowsMod);
nr_sdap_entity_update_qos_flows(req->gNB_cu_up_ue_id, &sdap);
}
if (to_modif->pdcp_config && to_modif->pdcp_config->pDCP_Reestablishment) {

View File

@@ -345,8 +345,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 = (session->sdap_config.default_drb == drb_ngran.id);
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.sdap_config.sDAP_Header_UL = !session->sdap_config.header_ul_absent;
drb_ngran.sdap_config.sDAP_Header_DL = !session->sdap_config.header_dl_absent;
drb_ngran.pdcp_config = set_bearer_context_pdcp_config(rrc_drb->pdcp_config, um_on_default_drb, redcap_cap);

View File

@@ -162,7 +162,7 @@ bool sdap_data_req(protocol_ctxt_t *ctxt_p,
rqi);
}
void sdap_data_ind(int pdcp_entity, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size)
void sdap_data_ind(int drb_id, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size)
{
nr_sdap_entity_t *sdap_entity;
sdap_entity = nr_sdap_get_entity(ue_id, pdusession_id);
@@ -172,13 +172,7 @@ void sdap_data_ind(int pdcp_entity, int is_gnb, int pdusession_id, ue_id_t ue_id
return;
}
sdap_entity->rx_entity(sdap_entity,
pdcp_entity,
is_gnb,
pdusession_id,
ue_id,
buf,
size);
sdap_entity->rx_entity(sdap_entity, drb_id, is_gnb, pdusession_id, ue_id, buf, size);
}
static void *sdap_tun_read_thread(void *arg)
@@ -213,14 +207,6 @@ static void *sdap_tun_read_thread(void *arg)
LOG_D(SDAP, "read data of size %d\n", len);
if (!entity->is_gnb && entity->enable_sdap && (entity->qfi < 0 || entity->qfi >= SDAP_MAX_QFI)) {
LOG_W(SDAP,
"Dropping UL SDU for UE %ld PDU session %d: no QoS rule QFI available for SDAP header\n",
entity->ue_id,
entity->pdusession_id);
continue;
}
protocol_ctxt_t ctxt = {.enb_flag = entity->is_gnb, .rntiMaybeUEid = entity->ue_id};
bool dc = entity->is_gnb ? false : SDAP_HDR_UL_DATA_PDU;

View File

@@ -37,7 +37,7 @@ bool sdap_data_req(protocol_ctxt_t *ctxt_p,
* Uplink - gNB
* Downlink - nrUE
*/
void sdap_data_ind(int pdcp_entity, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size);
void sdap_data_ind(int drb_id, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size);
void start_sdap_tun_gnb_first_ue_default_pdu_session(ue_id_t ue_id, int pdu_session_id);
void create_ue_ip_if(const char *ipv4, const char *ipv6, int ue_id, int pdu_session_id, bool is_default);

View File

@@ -43,14 +43,35 @@ static int get_sdap_role(bool is_gnb, const NR_SDAP_Config_t *sdap_config)
{
sdap_role_t role_ul = is_gnb ? SDAP_UL_RX : SDAP_UL_TX;
sdap_role_t role_dl = is_gnb ? SDAP_DL_TX : SDAP_DL_RX;
const bool ul_hdr = sdap_config->sdap_HeaderUL == NR_SDAP_Config__sdap_HeaderUL_present;
const bool dl_hdr = sdap_config->sdap_HeaderDL == NR_SDAP_Config__sdap_HeaderDL_present;
int role = NO_SDAP_HEADER;
if (sdap_config->sdap_HeaderUL == NR_SDAP_Config__sdap_HeaderUL_present)
if (ul_hdr)
role |= role_ul;
if (sdap_config->sdap_HeaderDL == NR_SDAP_Config__sdap_HeaderDL_present)
if (dl_hdr)
role |= role_dl;
LOG_D(SDAP,
"PDU session %ld %s: sdap-HeaderUL %s, sdap-HeaderDL %s (role 0x%x)\n",
sdap_config->pdu_Session,
is_gnb ? "gNB" : "UE",
ul_hdr ? "present" : "absent",
dl_hdr ? "present" : "absent",
role);
return role;
}
/** Return the first table row for drb_id (all QFIs on a DRB share entity_role) */
static const qfi2drb_t *nr_sdap_drb_lookup(const nr_sdap_entity_t *entity, int drb_id)
{
for (int qfi = 0; qfi < SDAP_MAX_QFI; qfi++) {
if (entity->qfi2drb_table[qfi].drb_id == drb_id)
return &entity->qfi2drb_table[qfi];
}
if (entity->default_drb.drb_id == drb_id)
return &entity->default_drb;
return NULL;
}
void nr_pdcp_submit_sdap_ctrl_pdu(ue_id_t ue_id, int sdap_ctrl_pdu_drb, nr_sdap_ul_hdr_t ctrl_pdu)
{
@@ -94,19 +115,20 @@ static bool nr_sdap_tx_entity(nr_sdap_entity_t *entity,
}
uint8_t sdap_buf[SDAP_MAX_PDU];
int drb_id = entity->qfi2drb_map(entity, qfi);
if (drb_id != SDAP_MAP_RULE_EMPTY) {
sdap_ul_tx = entity->qfi2drb_table[qfi].entity_role & SDAP_UL_TX; // UE TX entity
sdap_dl_tx = entity->qfi2drb_table[qfi].entity_role & SDAP_DL_TX; // gNB TX entity
LOG_D(SDAP, "TX - QFI: %u is mapped to DRB ID: %d\n", qfi, entity->qfi2drb_table[qfi].drb_id);
} else {
LOG_E(SDAP, "QFI %u not mapped to any DRB - dropping SDU of size %d\n", qfi, sdu_buffer_size);
const qfi2drb_t *map = entity->qfi2drb_map(entity, qfi);
if (!map) {
LOG_W(SDAP, "Dropping TX SDAP SDU: no DRB mapping for QFI %u (pdu_session=%d)\n", qfi, entity->pdusession_id);
return false;
}
const int drb_id = map->drb_id;
const int drb_role = map->entity_role;
sdap_ul_tx = drb_role & SDAP_UL_TX;
sdap_dl_tx = drb_role & SDAP_DL_TX;
LOG_D(SDAP, "TX - QFI: %u is mapped to DRB ID: %d\n", qfi, drb_id);
if (!sdap_ul_tx && !sdap_dl_tx) {
LOG_D(SDAP, "TX - DRB ID: %d does not have SDAP header\n", entity->qfi2drb_table[qfi].drb_id);
LOG_D(SDAP, "TX - DRB ID: %d does not have SDAP header\n", drb_id);
ret = nr_pdcp_data_req_drb(ctxt_p,
srb_flag,
drb_id,
@@ -192,55 +214,47 @@ static bool nr_sdap_tx_entity(nr_sdap_entity_t *entity,
return ret;
}
static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
int pdcp_entity,
int is_gnb,
int pdusession_id,
ue_id_t ue_id,
char *buf,
int size)
static void
nr_sdap_rx_entity(nr_sdap_entity_t *entity, int drb_id, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size)
{
bool sdap_ul_rx = false;
bool sdap_dl_rx = false;
int qfi = -1;
/* If SDAP header is disabled for this entity, bypass header parsing */
if (entity->enable_sdap) {
/** Extract QFI from SDAP header for UL direction
* Per TS 37.324 §6.2.2, QFI is carried in SDAP header when SDAP header is present
* This QFI will be used in GTP-U extension header (PDU Session Container)
* for N3-U tunnel (the first 6 bits in the first octet) */
qfi = buf[0] & 0x3F;
if (qfi >= SDAP_MAX_QFI) {
LOG_E(SDAP, "Invalid QFI %d received in SDAP header\n", qfi);
return;
}
// Accept SDAP packets only for QFIs that still have an active mapping.
const qfi2drb_t *mapping = &entity->qfi2drb_table[qfi];
/* If the QFI has no active mapping, drop the packet: it can happen
* after QoS-flow removal/reconfiguration due to packets that were already enqueued in PDCP */
if (mapping->drb_id == SDAP_NO_MAPPING_RULE) {
LOG_W(SDAP,
"Dropping UL SDAP PDU with unmapped QFI=%d (ue=%ld pdu_session=%d drb=%d)\n",
qfi,
ue_id,
pdusession_id,
pdcp_entity);
return;
}
sdap_ul_rx = mapping->entity_role & SDAP_UL_RX; // gNB RX entity
sdap_dl_rx = mapping->entity_role & SDAP_DL_RX; // UE RX entity
const qfi2drb_t *map = nr_sdap_drb_lookup(entity, drb_id);
if (!map) {
LOG_W(SDAP, "Dropping RX payload: DRB %d has no qfi2drb_table entry (ue=%ld, pdu_session=%d)\n", drb_id, ue_id, pdusession_id);
return;
}
const int drb_role = map->entity_role;
bool sdap_header_rx = is_gnb ? (drb_role & SDAP_UL_RX) : (drb_role & SDAP_DL_RX);
if (is_gnb) {
if (sdap_header_rx) {
/** Extract QFI from SDAP header for gNB UL RX
* Per TS 37.324 §6.2.2, QFI is carried in SDAP header when SDAP header is present
* This QFI will be used in GTP-U extension header (PDU Session Container)
* for N3-U tunnel (the first 6 bits in the first octet) */
const nr_sdap_ul_hdr_t *sdap_hdr = (const nr_sdap_ul_hdr_t *)buf;
qfi = sdap_hdr->QFI;
if (qfi >= SDAP_MAX_QFI) {
LOG_E(SDAP, "Invalid QFI %d received in SDAP header\n", qfi);
return;
}
/* TS 37.324 §5.2.1: with no stored rule, map to the default DRB when configured
* TS 38.331: default DRB always has a UL SDAP header
* Drop only when neither a stored rule nor default DRB applies to this QFI */
const qfi2drb_t *qfi_map = entity->qfi2drb_map(entity, qfi);
if (!qfi_map || qfi_map->drb_id != drb_id) {
LOG_W(SDAP,
"Dropping UL PDU: QFI=%d %s (ue=%ld, pdu_session=%d, receiving_drb=%d)\n",
qfi,
!qfi_map ? "no rule/default DRB" : "wrong DRB",
ue_id,
pdusession_id,
drb_id);
return;
}
if (is_gnb) { // gNB
/** QFI extracted from SDAP header for UL direction (N3-U tunnel)
* QFI range: 0..63 (6 bits) */
if (sdap_ul_rx) { // UL Data/Control PDU with SDAP header
DevAssert(qfi >= 0 && qfi < SDAP_MAX_QFI);
int offset = SDAP_HDR_LENGTH;
nr_sdap_ul_hdr_t *sdap_hdr = (nr_sdap_ul_hdr_t *)buf;
DevAssert(sdap_hdr->QFI == qfi);
LOG_D(SDAP, "RX Entity Received QFI: %u\n", qfi);
LOG_D(SDAP, "RX Entity Received QFI: %u\n", sdap_hdr->QFI);
LOG_D(SDAP, "RX Entity Received R bit: %u\n", sdap_hdr->R);
LOG_D(SDAP, "RX Entity Received DC bit: %u\n", sdap_hdr->DC);
@@ -279,7 +293,7 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
* if the DRB from which this SDAP data PDU is received is configured by RRC with the presence of SDAP header.
*/
int offset = 0;
if (sdap_dl_rx) { // DL Data/Control PDU with SDAP header
if (sdap_header_rx) { // DL Data/Control PDU with SDAP header
offset = SDAP_HDR_LENGTH;
/*
* TS 37.324 5.2 Data transfer
@@ -303,7 +317,7 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
* 5.3.2 Reflective mapping
* If there is no stored QoS flow to DRB mapping rule for the QoS flow and a default DRB is configured.
*/
if(!entity->qfi2drb_table[sdap_hdr->QFI].drb_id && entity->default_drb){
if (!entity->qfi2drb_table[sdap_hdr->QFI].drb_id && entity->default_drb.drb_id) {
nr_sdap_ul_hdr_t sdap_ctrl_pdu = entity->sdap_construct_ctrl_pdu(sdap_hdr->QFI);
int sdap_ctrl_pdu_drb = entity->sdap_map_ctrl_pdu(entity, SDAP_CTRL_PDU_MAP_DEF_DRB, sdap_hdr->QFI);
entity->sdap_submit_ctrl_pdu(ue_id, sdap_ctrl_pdu_drb, sdap_ctrl_pdu);
@@ -318,18 +332,19 @@ static void nr_sdap_rx_entity(nr_sdap_entity_t *entity,
* the DRB according to the stored QoS flow to DRB mapping rule is configured by RRC
* with the presence of UL SDAP header
*/
if (pdcp_entity != entity->qfi2drb_table[sdap_hdr->QFI].drb_id) {
if (drb_id != entity->qfi2drb_table[sdap_hdr->QFI].drb_id) {
nr_sdap_ul_hdr_t sdap_ctrl_pdu = entity->sdap_construct_ctrl_pdu(sdap_hdr->QFI);
int sdap_ctrl_pdu_drb = entity->sdap_map_ctrl_pdu(entity, SDAP_CTRL_PDU_MAP_RULE_DRB, sdap_hdr->QFI);
entity->sdap_submit_ctrl_pdu(ue_id, sdap_ctrl_pdu_drb, sdap_ctrl_pdu);
}
/*
* TS 37.324 5.3 QoS flow to DRB Mapping
* 5.3.2 Reflective mapping
* store the QoS flow to DRB mapping of the DL SDAP data PDU as the QoS flow to DRB mapping rule for the UL.
*/
entity->qfi2drb_table[sdap_hdr->QFI].drb_id = pdcp_entity;
* TS 37.324 §5.3.2 Reflective mapping: store the DL QoS flow to DRB mapping
* as the UL mapping rule. sdap-HeaderUL/DL are per DRB: copy the
* receiving DRB role onto the QFI row together with drb_id.
*/
entity->qfi2drb_table[sdap_hdr->QFI].drb_id = drb_id;
entity->qfi2drb_table[sdap_hdr->QFI].entity_role = drb_role;
}
/*
@@ -382,31 +397,31 @@ static void nr_sdap_qfi2drb_map_del(nr_sdap_entity_t *entity, const uint8_t qfi)
/**
* @brief get the DRB ID mapped to the QFI, for both DL and UL
* @return DRB that is mapped to the QFI
* or the default DRB if no mapping rule exists
* or 0 if no mapping and no default DRB exists for that QFI
* @return qfi2drb_table entry for the QFI {drb_id, entity_role},
* or the default DRB row if no mapping rule exists,
* or NULL if no mapping and no default DRB exists for that QFI
* @ref TS 37.324, 5.2.1 Uplink
* If there is no stored QoS flow to DRB mapping rule for the
* QoS flow as specified in the subclause 5.3, and a default DRB is configured,
* map the SDAP SDU to the default DRB else, map the SDAP SDU to the DRB according
* to the stored QoS flow to DRB mapping rule. */
static int nr_sdap_qfi2drb(nr_sdap_entity_t *entity, uint8_t qfi)
static const qfi2drb_t *nr_sdap_qfi2drb(const nr_sdap_entity_t *entity, uint8_t qfi)
{
/* Fetch DRB ID mapped to QFI */
int drb_id = entity->qfi2drb_table[qfi].drb_id;
if (drb_id) {
const qfi2drb_t *row = &entity->qfi2drb_table[qfi];
if (row->drb_id) {
/* QoS flow to DRB mapping rule exists, return corresponding DRB ID */
LOG_D(SDAP, "Existing QoS flow to DRB mapping rule: QFI %u to DRB %d\n", qfi, drb_id);
return drb_id;
} else if (entity->default_drb) {
/* QoS flow to DRB mapping rule does not exist, map SDAP SDU to default DRB, e.g. return default DRB of the SDAP entity */
LOG_D(SDAP, "QoS flow to DRB mapping rule does not exists! mapping SDU to Default DRB: %d\n", entity->default_drb);
return entity->default_drb;
LOG_D(SDAP, "Existing QoS flow to DRB mapping rule: QFI %u to DRB %d\n", qfi, row->drb_id);
return row;
} else if (entity->default_drb.drb_id) {
/* QoS flow to DRB mapping rule does not exist: map SDAP SDU to default DRB */
LOG_D(SDAP, "QoS flow to DRB mapping rule does not exists! mapping SDU to Default DRB: %d\n", entity->default_drb.drb_id);
return &entity->default_drb;
} else {
/* Note: UE undefined behaviour when neither a default DRB
nor a stored QoS flow to DRB mapping rule exists */
LOG_E(SDAP, "Mapping rule and default DRB do not exist for QFI:%u\n", qfi);
return SDAP_MAP_RULE_EMPTY;
return NULL;
}
}
@@ -425,11 +440,16 @@ int nr_sdap_map_ctrl_pdu(nr_sdap_entity_t *entity, int map_type, uint8_t dl_qfi)
{
int drb_of_endmarker = 0;
if(map_type == SDAP_CTRL_PDU_MAP_DEF_DRB){
drb_of_endmarker = entity->default_drb;
drb_of_endmarker = entity->default_drb.drb_id;
LOG_D(SDAP, "Mapping Control PDU QFI: %u to Default DRB: %d\n", dl_qfi, drb_of_endmarker);
}
if(map_type == SDAP_CTRL_PDU_MAP_RULE_DRB){
drb_of_endmarker = entity->qfi2drb_map(entity, dl_qfi);
const qfi2drb_t *map = entity->qfi2drb_map(entity, dl_qfi);
if (!map) {
LOG_E(SDAP, "No DRB mapping for control PDU QFI %u\n", dl_qfi);
return 0;
}
drb_of_endmarker = map->drb_id;
LOG_D(SDAP, "Mapping Control PDU QFI: %u to DRB: %d\n", dl_qfi, drb_of_endmarker);
}
return drb_of_endmarker;
@@ -454,7 +474,7 @@ static void nr_sdap_ue_control_pdu_config(nr_sdap_entity_t *entity, const ue_id_
for (int i = 0; i < sdap->mappedQFIs2AddCount; i++) {
uint8_t qfi = sdap->mappedQFIs2Add[i];
/* a default DRB exists and there is no stored QFI to DRB mapping rule for the QFI */
if (entity->default_drb && entity->qfi2drb_table[qfi].drb_id == SDAP_NO_MAPPING_RULE) {
if (entity->default_drb.drb_id && entity->qfi2drb_table[qfi].drb_id == SDAP_NO_MAPPING_RULE) {
// construct an end-marker control PDU (6.2.3 TS 37.324)
nr_sdap_ul_hdr_t sdap_ctrl_pdu = entity->sdap_construct_ctrl_pdu(qfi);
// map the end-marker control PDU to the default DRB
@@ -481,7 +501,7 @@ static void nr_sdap_add_qos_flows_to_drb(nr_sdap_entity_t *entity, const sdap_co
for (int i = 0; i < sdap->mappedQFIs2AddCount; i++) {
uint8_t qfi = sdap->mappedQFIs2Add[i];
LOG_D(SDAP, "Adding QFI to DRB mapping rules: %d mapped QFIs for DRB %d\n", sdap->mappedQFIs2AddCount, sdap->drb_id);
if (qfi < SDAP_MAX_QFI && sdap->drb_id > SDAP_MAP_RULE_EMPTY && sdap->drb_id <= MAX_DRBS_PER_UE) {
if (qfi < SDAP_MAX_QFI && sdap->drb_id > SDAP_NO_MAPPING_RULE && sdap->drb_id <= MAX_DRBS_PER_UE) {
entity->qfi2drb_map_add(entity, qfi, sdap->drb_id, sdap->role);
} else {
LOG_E(SDAP, "Failed to add qfi2drb mapping: QFI=%d, DRB=%d\n", qfi, sdap->drb_id);
@@ -504,24 +524,31 @@ static void nr_sdap_rm_qos_flows_from_drb(nr_sdap_entity_t *entity, const sdap_c
* @param drb the DRB ID to be mapped */
static void nr_sdap_qfi2drb_map_update(nr_sdap_entity_t *entity, const sdap_config_t *sdap)
{
/* disabled SDAP: only one DRB per PDU session existing qfi2drb_table entries must match the DRB to be updated */
if (!entity->enable_sdap) {
for (int i = 0; i < SDAP_MAX_QFI; i++) {
int mapped = entity->qfi2drb_table[i].drb_id;
if (mapped != SDAP_NO_MAPPING_RULE) {
AssertFatal(mapped == sdap->drb_id,
"PDU session %d: disabled SDAP allows only one DRB per PDU session (qfi2drb_table[%d] maps to DRB %d)\n",
entity->pdusession_id,
i,
mapped);
}
}
}
if (!entity->is_gnb) { // UE control PDU configuration
nr_sdap_ue_control_pdu_config(entity, entity->ue_id, sdap);
}
nr_sdap_add_qos_flows_to_drb(entity, sdap);
nr_sdap_rm_qos_flows_from_drb(entity, sdap);
/* TS 38.331: all QFIs on a DRB share the same sdap-HeaderUL/DL role */
for (int i = 0; i < SDAP_MAX_QFI; i++) {
if (entity->qfi2drb_table[i].drb_id == sdap->drb_id)
entity->qfi2drb_table[i].entity_role = sdap->role;
}
if (sdap->role == NO_SDAP_HEADER) {
/* TS 37.324 §6.2.2.1: with both headers absent, only one DRB per PDU session is allowed */
int mapped_drbs = 0;
for (int drb = 1; drb <= MAX_DRBS_PER_UE; drb++) {
for (int qfi = 0; qfi < SDAP_MAX_QFI; qfi++) {
if (entity->qfi2drb_table[qfi].drb_id == drb) {
mapped_drbs++;
break;
}
}
}
AssertFatal(mapped_drbs <= 1, "PDU session %d: disabled SDAP but %d DRBs mapped\n", entity->pdusession_id, mapped_drbs);
}
}
/**
@@ -540,7 +567,6 @@ static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap
sdap_entity->ue_id = ue_id;
sdap_entity->pdusession_id = sdap->pdusession_id;
sdap_entity->is_gnb = is_gnb;
sdap_entity->enable_sdap = (sdap->role != NO_SDAP_HEADER);
// rx/tx entities
sdap_entity->tx_entity = nr_sdap_tx_entity;
@@ -560,13 +586,14 @@ static void nr_sdap_add_entity(const int is_gnb, const ue_id_t ue_id, const sdap
// set default DRB
if (sdap->defaultDRB) {
sdap_entity->default_drb = sdap->drb_id;
LOG_I(SDAP, "Default DRB for the created SDAP entity: DRB %d \n", sdap_entity->default_drb);
sdap_entity->default_drb.drb_id = sdap->drb_id;
sdap_entity->default_drb.entity_role = sdap->role;
LOG_I(SDAP, "Default DRB for the created SDAP entity: DRB %d \n", sdap_entity->default_drb.drb_id);
}
if (!sdap_entity->enable_sdap) {
if (sdap->role == NO_SDAP_HEADER) {
AssertFatal(sdap->mappedQFIs2AddCount <= 1,
"PDU session %d: disabled SDAP allows at most one QoS flow to add at entity creation\n",
"PDU session %d: both SDAP headers absent: at most one QoS flow at entity creation\n",
sdap->pdusession_id);
}
@@ -645,61 +672,46 @@ void nr_sdap_release_drb(ue_id_t ue_id, int drb_id, int pdusession_id)
* On E1 Bearer Context Modification (CP to UP), Flow Mapping Information in DRB To Modify
* carries the QoS Flow QoS Parameters List for that DRB. Per TS 38.463, when present the
* CU-UP replaces the previous mapping for that DRB.
* Remove every QFI currently mapped to the DRB that is absent from the list of QFIs to be mapped.
* @param qfis[] list of QFIs to be mapped to the DRB
* @param n_qfis number of QFIs in the list
* @param drb_id the DRB ID to be mapped */
void nr_sdap_entity_update_qos_flows(ue_id_t ue_id, int pdusession_id, int drb_id, const uint8_t *qfis, int n_qfis)
* @param sdap mapped QoS flows to add */
void nr_sdap_entity_update_qos_flows(ue_id_t ue_id, sdap_config_t *sdap)
{
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, pdusession_id);
DevAssert(sdap);
nr_sdap_entity_t *entity = nr_sdap_get_entity(ue_id, sdap->pdusession_id);
if (!entity) {
LOG_W(SDAP, "gNB SDAP: no entity for UE %lu PDU session %d when updating QoS flows for DRB %d\n", ue_id, pdusession_id, drb_id);
LOG_W(SDAP, "No entity when updating QoS flows for DRB %d (pdu_session=%d)\n", sdap->drb_id, sdap->pdusession_id);
return;
}
/* Derive SDAP role from entity configuration */
uint8_t role = NO_SDAP_HEADER;
if (entity->enable_sdap) {
if (entity->is_gnb) {
role |= SDAP_UL_RX;
role |= SDAP_DL_TX;
} else {
role |= SDAP_UL_TX;
role |= SDAP_DL_RX;
}
const qfi2drb_t *drb_map = nr_sdap_drb_lookup(entity, sdap->drb_id);
if (!drb_map) {
LOG_E(SDAP, "DRB %d: no qfi2drb_table entry, skip QoS-flow mapping update (pdu_session=%d)\n", sdap->drb_id, sdap->pdusession_id);
return;
}
/* TS 38.331: sdap-HeaderUL/DL cannot change after DRB establishment */
sdap->role = drb_map->entity_role;
sdap->defaultDRB = (entity->default_drb.drb_id == sdap->drb_id);
sdap_config_t sdap = {.pdusession_id = pdusession_id,
.drb_id = drb_id,
.role = role,
.defaultDRB = (entity->default_drb == drb_id),
.mappedQFIs2AddCount = n_qfis};
/* Build list of QFIs to be released */
/* TS 37.324 clause 5.3.1 mappedQoS-FlowsToRelease: QFIs on this DRB not in the new list */
for (int q = 0; q < SDAP_MAX_QFI; q++) {
if (entity->qfi2drb_table[q].drb_id != drb_id)
if (entity->qfi2drb_table[q].drb_id != sdap->drb_id)
continue;
bool keep = false;
for (int i = 0; i < n_qfis; i++) {
if (qfis[i] == q) {
for (int i = 0; i < sdap->mappedQFIs2AddCount; i++) {
if (sdap->mappedQFIs2Add[i] == q) {
keep = true;
break;
}
}
if (!keep) {
DevAssert(sdap.mappedQFIs2ReleaseCount < SDAP_MAX_QFI);
sdap.mappedQFIs2Release[sdap.mappedQFIs2ReleaseCount++] = q;
LOG_I(SDAP, "gNB SDAP mapping update: UE %lu pduSession %d DRB %d release_qfi=%d\n", ue_id, pdusession_id, drb_id, q);
DevAssert(sdap->mappedQFIs2ReleaseCount < SDAP_MAX_QFI);
sdap->mappedQFIs2Release[sdap->mappedQFIs2ReleaseCount++] = q;
LOG_I(SDAP, "Update: release QFI %d from DRB %d (pdu_session=%d)\n", q, sdap->drb_id, sdap->pdusession_id);
}
}
/* Build list of QFIs to be added */
for (int i = 0; i < n_qfis; i++)
sdap.mappedQFIs2Add[i] = qfis[i];
LOG_I(SDAP, "gNB SDAP mapping update: UE %lu pduSession %d DRB %d n_qfis=%d\n", ue_id, pdusession_id, drb_id, n_qfis);
entity->qfi2drb_map_update(entity, &sdap);
LOG_I(SDAP, "Update: add %d QFIs to DRB %d (pdu_session=%d)\n", sdap->mappedQFIs2AddCount, sdap->drb_id, sdap->pdusession_id);
/* TS 37.324 clause 5.3.1: mappedQoS-FlowsToAdd and apply mapping on the DRB */
entity->qfi2drb_map_update(entity, sdap);
}
bool nr_sdap_delete_entity(ue_id_t ue_id, int pdusession_id)
@@ -783,7 +795,6 @@ sdap_config_t nr_sdap_get_config(const int is_gnb, const NR_SDAP_Config_t *sdap_
sdap_config_t sdapConfig = {0};
sdapConfig.drb_id = drb_id;
sdapConfig.role = get_sdap_role(is_gnb, sdap_Config);
LOG_D(SDAP, "SDAP headers %s\n", sdapConfig.role == NO_SDAP_HEADER ? "absent" : "present");
sdapConfig.defaultDRB = sdap_Config->defaultDRB;
// 3GPP TS 38.331 The network sets sdap-HeaderUL to present if the field defaultDRB is set to true
if (sdapConfig.defaultDRB && (sdap_Config->sdap_HeaderUL != NR_SDAP_Config__sdap_HeaderUL_present))

View File

@@ -20,7 +20,6 @@
#define SDAP_HDR_UL_CTRL_PDU (0)
#define SDAP_HDR_LENGTH (1)
#define SDAP_MAX_QFI (64)
#define SDAP_MAP_RULE_EMPTY (0)
#define SDAP_NO_MAPPING_RULE (0)
#define SDAP_REFLECTIVE_MAPPING (1)
#define SDAP_RQI_HANDLING (1)
@@ -78,17 +77,16 @@ typedef struct sdap_configuration_s {
typedef struct nr_sdap_entity_s {
ue_id_t ue_id;
int default_drb;
/// sdap_tun_read_thread needs to know if we are gNB/UE, so for noS1 mode,
/// store which one we are
bool is_gnb;
bool enable_sdap;
int pdusession_id;
int pdusession_sock;
pthread_t pdusession_thread;
bool stop_thread;
int qfi;
qfi2drb_t default_drb;
qfi2drb_t qfi2drb_table[SDAP_MAX_QFI];
void (*qfi2drb_map_update)(struct nr_sdap_entity_s *entity, const sdap_config_t *sdap);
@@ -97,7 +95,7 @@ typedef struct nr_sdap_entity_s {
const uint8_t qfi,
const uint8_t drb_id,
const uint8_t role);
int (*qfi2drb_map)(struct nr_sdap_entity_s *entity, uint8_t qfi);
const qfi2drb_t *(*qfi2drb_map)(const struct nr_sdap_entity_s *entity, uint8_t qfi);
nr_sdap_ul_hdr_t (*sdap_construct_ctrl_pdu)(uint8_t qfi);
int (*sdap_map_ctrl_pdu)(struct nr_sdap_entity_s *entity, int map_type, uint8_t dl_qfi);
@@ -116,13 +114,7 @@ typedef struct nr_sdap_entity_s {
const uint8_t qfi,
const bool rqi);
void (*rx_entity)(struct nr_sdap_entity_s *entity,
int pdcp_entity,
int is_gnb,
int pdusession_id,
ue_id_t ue_id,
char *buf,
int size);
void (*rx_entity)(struct nr_sdap_entity_s *entity, int drb_id, int is_gnb, int pdusession_id, ue_id_t ue_id, char *buf, int size);
/* List of entities */
struct nr_sdap_entity_s *next_entity;
@@ -182,7 +174,7 @@ bool nr_sdap_delete_ue_entities(ue_id_t ue_id);
*/
void nr_reconfigure_sdap_entity(NR_SDAP_Config_t *sdap_config, ue_id_t ue_id, int pdusession_id, int drb_id);
void nr_sdap_entity_update_qos_flows(ue_id_t ue_id, int pdusession_id, int drb_id, const uint8_t *qfis, int n_qfis);
void nr_sdap_entity_update_qos_flows(ue_id_t ue_id, sdap_config_t *sdap);
void set_qfi(uint8_t qfi, uint8_t pduid, ue_id_t ue_id);
#endif