QoS handling: add Dynamic5QI QoS support/validation, split QoS characteristics by 5QI type

Model non-dynamic vs dynamic 5QI characteristics explicitly and propagate the
new layout through NGAP decode and RRC bearer/QoS handling.

Changes:
- Define `non_dynamic_5qi_t`/`dynamic_5qi_t`, PER/PDB bounds, and embed a
  `qos_characteristics` union in `pdusession_level_qos_parameter_t`
- Populate the new QoS structures in `fill_qos()`, including optional
  allocations for Dynamic 5QI `fiveQI` and NonDynamic `priorityLevelQos`
- Map QoS params to F1AP with `nr_rrc_get_f1_qos_flow_param()` and add range
  validation for dynamic priority/PDB/PER and non-dynamic 5QI
- Populate E1 QoS characteristics from the new layout and update QoS modify
  handling to manage optional pointer fields (`openair2/RRC/NR/rrc_gNB_NGAP.c`)
- Derive a numeric 5QI via `get_qos_fiveqi()`, handle missing-5QI dynamic flows
  conservatively, and extend dedicated-DRB decisions to fall back to dynamic
  characteristics
- Add a 5QI range assert in F1AP QoS encoding and extend bearer tests with a
  Dynamic 5QI flow

Signed-off-by: Guido Casati <guido.casati@openairinterface.org>
This commit is contained in:
Guido Casati
2025-11-07 23:29:15 +01:00
parent 4ad557a0e3
commit ef971849bd
8 changed files with 298 additions and 57 deletions

View File

@@ -90,11 +90,56 @@ typedef uint8_t qos_priority_level_t;
#define MIN_QOS_PRIORITY_LEVEL 1 // highest priority
#define MAX_QOS_PRIORITY_LEVEL 127 // lowest priority
/* Packet Delay Budget (PDB) - 3GPP TS 23.501 §5.7.3.4
* Upper bound for packet delay between UE and UPF N6 termination point (0..1023 ms).
* Used for scheduling and link layer configuration (e.g. HARQ target operating points).
* For Delay-critical GBR flows, packets delayed more than PDB are counted as lost. */
#define MIN_PACKET_DELAY_BUDGET 0
#define MAX_PACKET_DELAY_BUDGET 1023
/* Packet Error Rate (PER) - 3GPP TS 23.501 §5.7.3.5
* Upper bound for rate of non-congestion related packet losses (0..9 for scalar/exponent).
* Used for link layer protocol configuration (e.g. RLC, HARQ).
* PER = Scalar * 10^(-Exponent) */
#define MIN_PACKET_ERROR_RATE_SCALAR 0
#define MAX_PACKET_ERROR_RATE_SCALAR 9
#define MIN_PACKET_ERROR_RATE_EXPONENT 0
#define MAX_PACKET_ERROR_RATE_EXPONENT 9
typedef struct {
// Packet Error Rate Scalar (0..9)
uint8_t scalar;
// Packet Error Rate Exponent (0..9)
uint8_t exponent;
} qos_per_t;
/** QoS Characteristics for a standardized or
* pre-configured 5QI for downlink and uplink*/
typedef struct {
uint16_t fiveQI;
qos_priority_level_t *qos_priority;
} non_dynamic_5qi_t;
/** QoS Characteristics for a Non-standardised or
* not pre-configured 5QI for downlink and uplink. */
typedef struct {
uint16_t *fiveQI;
qos_priority_level_t qos_priority;
// Packet Delay Budget (0..1023 ms)
int packet_delay_budget;
// Packet Error Rate (0..9 for scalar/exponent)
qos_per_t per;
} dynamic_5qi_t;
typedef struct pdusession_level_qos_parameter_s {
uint8_t qfi;
uint64_t fiveQI;
qos_priority_level_t qos_priority;
// QoS Characteristics
fiveQI_t fiveQI_type;
union {
non_dynamic_5qi_t non_dynamic;
dynamic_5qi_t dynamic;
} qos_characteristics;
// NG-RAN Allocation and Retention Priority
qos_arp_t arp;
} pdusession_level_qos_parameter_t;

View File

@@ -386,6 +386,7 @@ static F1AP_QoSFlowLevelQoSParameters_t encode_qos_flow_param(const f1ap_qos_flo
if (p->qos_type == NON_DYNAMIC) {
f1ap.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
asn1cCalloc(f1ap.qoS_Characteristics.choice.non_Dynamic_5QI, nd);
DevAssert(p->nondyn.fiveQI >= MIN_FIVEQI && p->nondyn.fiveQI <= MAX_FIVEQI);
nd->fiveQI = p->nondyn.fiveQI;
} else {
DevAssert(p->qos_type == DYNAMIC);

View File

@@ -88,7 +88,6 @@ byte_array_t rrc_gNB_encode_RRCReconfiguration(gNB_RRC_UE_t *UE, nr_rrc_reconfig
nr_rrc_reconfig_param_t get_RRCReconfiguration_params(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, uint8_t srb_reest_bitmap, bool drb_reestablish);
f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param);
void openair_rrc_gNB_configuration(gNB_RRC_INST *rrc, nr_rrc_config_t *configuration);
NR_SRB_ToAddModList_t *createSRBlist(gNB_RRC_UE_t *ue, uint8_t reestablish);
NR_DRB_ToAddModList_t *createDRBlist(gNB_RRC_UE_t *ue, bool reestablish, bool do_integrity, bool do_ciphering);

View File

@@ -2887,21 +2887,44 @@ unsigned int mask_flip(unsigned int x) {
return((((x>>8) + (x<<8))&0xffff)>>6);
}
/* \bref return F1AP QoS characteristics based on Qos flow parameters */
f1ap_qos_flow_param_t get_qos_char_from_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
/** @brief Get F1AP QoS flow parameters from PDU session QoS parameters
* @param qos_param PDU session level QoS parameters from NGAP
* @return F1AP QoS flow parameters */
static f1ap_qos_flow_param_t nr_rrc_get_f1_qos_flow_param(const pdusession_level_qos_parameter_t *qos_param)
{
f1ap_qos_flow_param_t qos_char = {0};
if (qos_param->fiveQI_type == DYNAMIC) {
if (qos_param->fiveQI_type == DYNAMIC) { // Dynamic 5QI
const dynamic_5qi_t *dyn = &qos_param->qos_characteristics.dynamic;
qos_char.qos_type = DYNAMIC;
qos_char.dyn.prio = qos_param->qos_priority;
qos_char.dyn.pdb = 100; // TODO
qos_char.dyn.per.scalar = 10; // TODO
qos_char.dyn.per.exponent = 100; // TODO
} else {
// Mandatory for Dynamic5QI (range 1..127)
DevAssert(dyn->qos_priority >= MIN_QOS_PRIORITY_LEVEL && dyn->qos_priority <= MAX_QOS_PRIORITY_LEVEL);
qos_char.dyn.prio = dyn->qos_priority;
// Packet Delay Budget (0..1023)
DevAssert(dyn->packet_delay_budget >= MIN_PACKET_DELAY_BUDGET && dyn->packet_delay_budget <= MAX_PACKET_DELAY_BUDGET);
qos_char.dyn.pdb = dyn->packet_delay_budget;
// Packet Error Rate (0..9 for scalar/exponent)
const qos_per_t *per = &dyn->per;
DevAssert(per->scalar >= MIN_PACKET_ERROR_RATE_SCALAR && per->scalar <= MAX_PACKET_ERROR_RATE_SCALAR);
DevAssert(per->exponent >= MIN_PACKET_ERROR_RATE_EXPONENT && per->exponent <= MAX_PACKET_ERROR_RATE_EXPONENT);
qos_char.dyn.per.scalar = per->scalar;
qos_char.dyn.per.exponent = per->exponent;
} else { // Non-Dynamic 5QI
const non_dynamic_5qi_t *non_dyn = &qos_param->qos_characteristics.non_dynamic;
qos_char.qos_type = NON_DYNAMIC;
qos_char.nondyn.fiveQI = qos_param->fiveQI;
// 5QI (0..255)
const uint16_t five_qi = non_dyn->fiveQI;
DevAssert(five_qi >= MIN_FIVEQI && five_qi <= MAX_FIVEQI);
qos_char.nondyn.fiveQI = five_qi;
// Note: F1AP non-dynamic 5QI doesn't have priority field
}
// Allocation and Retention Priority (ARP)
const qos_arp_t *a = &qos_param->arp;
// ARP Priority Level (1..15)
DevAssert(a->priority_level >= MIN_QOS_ARP_PRIORITY_LEVEL && a->priority_level <= MAX_QOS_ARP_PRIORITY_LEVEL);
qos_char.arp.prio = a->priority_level;
qos_char.arp.preempt_cap =
a->pre_emp_capability == PEC_MAY_TRIGGER_PREEMPTION ? MAY_TRIGGER_PREEMPTION : SHALL_NOT_TRIGGER_PREEMPTION;
@@ -2953,7 +2976,7 @@ static f1ap_drb_info_nr_t fill_f1_drb_info_nr(pdusession_t *pdu, const uint8_t *
/* 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);
flow->param = nr_rrc_get_f1_qos_flow_param(&qos_param->qos);
// Find flow with highest ARP priority (lowest ARP priority_level value)
// (lower ARP priority value = higher priority for admission/preemption)
if (highest_priority_flow == NULL || flow->param.arp.prio < highest_arp) {

View File

@@ -11,6 +11,7 @@
#include <netinet/sctp.h>
#include <openair3/ocp-gtpu/gtp_itf.h>
#include <stdbool.h>
#include "common/utils/utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -285,12 +286,22 @@ static qos_flow_to_setup_t fill_e1_qos_flow_to_setup(const pdusession_level_qos_
// 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;
const non_dynamic_5qi_t *non_dynamic = &qos->qos_characteristics.non_dynamic;
typeof(qos_characteristics->non_dynamic) *out_non_dynamic = &qos_characteristics->non_dynamic;
out_non_dynamic->fiveqi = non_dynamic->fiveQI;
if (non_dynamic->qos_priority) {
out_non_dynamic->qos_priority_level = *non_dynamic->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
const dynamic_5qi_t *dynamic = &qos->qos_characteristics.dynamic;
typeof(qos_characteristics->dynamic) *out_dynamic = &qos_characteristics->dynamic;
if (dynamic->fiveQI != NULL) {
out_dynamic->fiveqi = *dynamic->fiveQI;
}
out_dynamic->qos_priority_level = dynamic->qos_priority;
out_dynamic->packet_delay_budget = dynamic->packet_delay_budget;
out_dynamic->packet_error_rate.per_scalar = dynamic->per.scalar;
out_dynamic->packet_error_rate.per_exponent = dynamic->per.exponent;
}
qos_characteristics->qos_type = qos->fiveQI_type;
@@ -943,19 +954,50 @@ static void nr_rrc_update_qos(seq_arr_t *list, const int nb_qos, const pdusessio
}
// Validate 5QI value
if (!is_5qi_standardized(q_in->fiveQI)) {
const non_dynamic_5qi_t *in_non_dynamic = &q_in->qos_characteristics.non_dynamic;
const dynamic_5qi_t *in_dynamic = &q_in->qos_characteristics.dynamic;
if (q_in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in_non_dynamic->fiveQI)) {
LOG_W(NR_RRC,
"QoS flow QFI=%d: 5QI %lu is not a standardized value (1-9, 65-90). Skipping QoS flow.\n",
"QoS flow QFI=%d: 5QI %u is not a standardized value (1-9, 65-90). Skipping QoS flow.\n",
q_in->qfi,
q_in->fiveQI);
in_non_dynamic->fiveQI);
continue;
}
nr_rrc_qos_t *qos = find_qos(list, q_in->qfi);
if (qos) {
pdusession_level_qos_parameter_t *dst_qos = &qos->qos;
AssertFatal(qos->qos.qfi == q_in->qfi, "QoS Flow to modify must match existing one");
LOG_I(NR_RRC, "Updating QoS for QFI=%d\n", q_in->qfi);
qos->qos = *q_in;
DevAssert(dst_qos->fiveQI_type == q_in->fiveQI_type);
dst_qos->qfi = q_in->qfi;
dst_qos->arp = q_in->arp;
if (q_in->fiveQI_type == DYNAMIC) {
dynamic_5qi_t *out_dyn = &dst_qos->qos_characteristics.dynamic;
out_dyn->qos_priority = in_dynamic->qos_priority;
out_dyn->packet_delay_budget = in_dynamic->packet_delay_budget;
out_dyn->per = in_dynamic->per;
if (in_dynamic->fiveQI == NULL) {
free_and_zero(out_dyn->fiveQI);
} else {
if (out_dyn->fiveQI == NULL)
out_dyn->fiveQI = calloc_or_fail(1, sizeof(*out_dyn->fiveQI));
*out_dyn->fiveQI = *in_dynamic->fiveQI;
}
} else { /* NON_DYNAMIC */
non_dynamic_5qi_t *out_non_dyn = &dst_qos->qos_characteristics.non_dynamic;
out_non_dyn->fiveQI = in_non_dynamic->fiveQI;
if (in_non_dynamic->qos_priority == NULL) {
free_and_zero(out_non_dyn->qos_priority);
} else {
if (out_non_dyn->qos_priority == NULL)
out_non_dyn->qos_priority = calloc_or_fail(1, sizeof(*out_non_dyn->qos_priority));
*out_non_dyn->qos_priority = *in_non_dynamic->qos_priority;
}
}
} else {
LOG_E(NR_RRC, "Failed to update QoS for QFI=%d: QoS flow not found\n", q_in->qfi);
}

View File

@@ -11,11 +11,20 @@
#include "assertions.h"
#include "common/platform_constants.h"
#include "common/utils/T/T.h"
#include "common/utils/utils.h"
#include "ngap_messages_types.h"
#include "oai_asn1.h"
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_asn1_utils.h"
#include "common/utils/alg/find.h"
#define NO_FIVEQI UINT16_MAX
/* Implementation policy: dedicated-DRB heuristic for Dynamic 5QI without a numeric 5QI.
* These thresholds are not specified by 3GPP but reflect a conservative mapping choice. */
#define DYN5QI_DEDICATED_DRB_PDB_THRESHOLD_MS 50
#define DYN5QI_DEDICATED_DRB_PER_SCALAR_MAX 1
#define DYN5QI_DEDICATED_DRB_PER_EXPONENT_MIN 6
#define DYN5QI_DEDICATED_DRB_QOS_PRIORITY_MAX 10
static bool eq_qfi(const void *vval, const void *vit)
{
const int id = *(const int *)vval;
@@ -23,6 +32,19 @@ static bool eq_qfi(const void *vval, const void *vit)
return elem->qos.qfi == id;
}
static uint16_t get_qos_fiveqi(const pdusession_level_qos_parameter_t *qos)
{
if (qos->fiveQI_type == NON_DYNAMIC) {
DevAssert(qos->qos_characteristics.non_dynamic.fiveQI <= MAX_STANDARDIZED_FIVEQI);
return qos->qos_characteristics.non_dynamic.fiveQI;
}
if (qos->qos_characteristics.dynamic.fiveQI != NULL) {
DevAssert(*qos->qos_characteristics.dynamic.fiveQI <= MAX_FIVEQI);
return *qos->qos_characteristics.dynamic.fiveQI;
}
return NO_FIVEQI;
}
/** @brief Retrieves mapped QoS in UE context for the input @param qfi
* @return pointer to the found QoS, NULL if not found */
nr_rrc_qos_t *find_qos(seq_arr_t *seq, int qfi)
@@ -63,12 +85,27 @@ nr_rrc_qos_t *add_qos(seq_arr_t *qos, const pdusession_level_qos_parameter_t *in
}
// Validate 5QI value (TS 23.501 allows dynamically assigned 5QIs)
if (in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in->fiveQI)) {
LOG_W(NR_RRC, "QoS flow QFI=%d: 5QI %ld is not a standardized value. Skipping QoS flow.\n", in->qfi, in->fiveQI);
if (in->fiveQI_type == NON_DYNAMIC && !is_5qi_standardized(in->qos_characteristics.non_dynamic.fiveQI)) {
LOG_W(NR_RRC,
"QoS flow QFI=%d: 5QI %d is not a standardized value. Skipping QoS flow.\n",
in->qfi,
in->qos_characteristics.non_dynamic.fiveQI);
return NULL;
}
nr_rrc_qos_t item = {.qos = *in};
const dynamic_5qi_t *in_dyn = &in->qos_characteristics.dynamic;
dynamic_5qi_t *out_dyn = &item.qos.qos_characteristics.dynamic;
if (in->fiveQI_type == DYNAMIC && in_dyn->fiveQI != NULL) {
out_dyn->fiveQI = calloc_or_fail(1, sizeof(*out_dyn->fiveQI));
*out_dyn->fiveQI = *in_dyn->fiveQI;
}
const non_dynamic_5qi_t *in_non_dyn = &in->qos_characteristics.non_dynamic;
non_dynamic_5qi_t *out_non_dyn = &item.qos.qos_characteristics.non_dynamic;
if (in->fiveQI_type == NON_DYNAMIC && in_non_dyn->qos_priority != NULL) {
out_non_dyn->qos_priority = calloc_or_fail(1, sizeof(*out_non_dyn->qos_priority));
*out_non_dyn->qos_priority = *in_non_dyn->qos_priority;
}
seq_arr_push_back(qos, &item, sizeof(nr_rrc_qos_t));
// Double check successful add
@@ -82,8 +119,11 @@ nr_rrc_qos_t *add_qos(seq_arr_t *qos, const pdusession_level_qos_parameter_t *in
/** @brief Free QoS flows list items */
static void free_qos(void *ptr)
{
/*nothing to do*/
UNUSED(ptr);
nr_rrc_qos_t *q = ptr;
if (q->qos.fiveQI_type == DYNAMIC)
free_and_zero(q->qos.qos_characteristics.dynamic.fiveQI);
if (q->qos.fiveQI_type == NON_DYNAMIC)
free_and_zero(q->qos.qos_characteristics.non_dynamic.qos_priority);
}
/** @brief Free QoS flows list */
@@ -401,22 +441,40 @@ bool nr_rrc_is_non_gbr_fiveqi(uint16_t five_qi)
// Design-specific aggregate cap to prevent over-multiplexing when mixing resource types on the same DRB
#define MAX_QOS_FLOWS_PER_DRB_TOTAL 5
/** @brief Check if 5QI requires dedicated DRB based on QoS characteristics
* Delay-critical GBR (5QI 82-90) and high priority and low-PER services
* (e.g. mission-critical and live streaming flows) get dedicated DRBs. */
static bool nr_rrc_qos_dedicated_drb(const uint8_t five_qi)
/** @brief Decide whether a QoS flow should get a dedicated DRB.
*
* - If a numeric 5QI is available (Non-Dynamic 5QI, or Dynamic 5QI with 5QI present), we apply
* the implementation's 5QI-based isolation policy. In particular, Delay-Critical GBR (5QI 82-90)
* and selected high priority / low-PER services are isolated on dedicated DRBs.
*
* - For Dynamic 5QI where the numeric 5QI is absent, 3GPP allows relying on the signaled QoS
* characteristics. We use a conservative heuristic based on priority, PDB and PER thresholds */
static bool nr_rrc_qos_dedicated_drb(const pdusession_level_qos_parameter_t *qos)
{
DevAssert(five_qi <= MAX_FIVEQI);
// Delay-critical GBR (5QI 82-90) get dedicated DRBs
qos_resource_type_t type = five_qi_resource_type[five_qi];
if (type == QOS_RESOURCE_TYPE_DC_GBR) {
return true;
const uint16_t five_qi = get_qos_fiveqi(qos);
if (five_qi != NO_FIVEQI) {
if (!is_5qi_standardized(five_qi)) {
return true; // Dynamic non-standardized 5QI: conservative behavior to prevent unintended multiplexing
}
// Delay-critical GBR (5QI 82-90) get dedicated DRBs
qos_resource_type_t type = five_qi_resource_type[five_qi];
if (type == QOS_RESOURCE_TYPE_DC_GBR)
return true;
// High priority and low-PER services get dedicated DRBs
return (five_qi == 4 || five_qi == 6 || five_qi == 7 || five_qi == 8 || five_qi == 9 || five_qi == 10 || five_qi == 70
|| five_qi == 80 || (five_qi >= 71 && five_qi <= 73));
}
// High priority and low-PER services get dedicated DRBs
return (five_qi == 4 || five_qi == 6 || five_qi == 7 || five_qi == 8
|| five_qi == 9 || five_qi == 10 || five_qi == 70
|| five_qi == 80 || (five_qi >= 71 && five_qi <= 73));
// Dynamic 5QI without a numeric 5QI: decide based on signaled characteristics.
DevAssert(qos->fiveQI_type == DYNAMIC);
const dynamic_5qi_t *dyn = &qos->qos_characteristics.dynamic;
const qos_per_t *per = &dyn->per;
/* Conservative policy for low-latency / high-reliability / high-priority flows. */
const bool low_latency = dyn->packet_delay_budget <= DYN5QI_DEDICATED_DRB_PDB_THRESHOLD_MS;
const bool high_reliability =
(per->scalar <= DYN5QI_DEDICATED_DRB_PER_SCALAR_MAX && per->exponent >= DYN5QI_DEDICATED_DRB_PER_EXPONENT_MIN);
const bool high_priority = dyn->qos_priority <= DYN5QI_DEDICATED_DRB_QOS_PRIORITY_MAX;
return low_latency || high_reliability || high_priority;
}
/** @brief Count QoS flows mapped to a specific DRB, grouped by resource type.
@@ -443,8 +501,14 @@ static void nr_rrc_count_qos_flows_by_type(const seq_arr_t *qos_flows,
// Check if the QoS flow is mapped to the given DRB ID
if (qos->drb_id == drb_id) {
// Increment the counter depending on the resource type of the QoS flow
const uint8_t five_qi = qos->qos.fiveQI;
DevAssert(five_qi <= MAX_FIVEQI);
const uint16_t five_qi = get_qos_fiveqi(&qos->qos);
if (five_qi == NO_FIVEQI || !is_5qi_standardized(five_qi)) {
/* Dynamic 5QI may omit the numeric 5QI; in that case we can't classify by 5QI-derived resource type.
* Count conservatively to avoid reusing/multiplexing such flows on existing DRBs. */
(*dc_gbr_count)++;
continue;
}
DevAssert(five_qi <= MAX_STANDARDIZED_FIVEQI);
qos_resource_type_t type = five_qi_resource_type[five_qi];
switch (type) {
case QOS_RESOURCE_TYPE_DC_GBR:
@@ -457,7 +521,7 @@ static void nr_rrc_count_qos_flows_by_type(const seq_arr_t *qos_flows,
(*non_gbr_count)++;
break;
default:
LOG_W(NR_RRC, "Unknown resource type for 5QI %lu\n", qos->qos.fiveQI);
LOG_W(NR_RRC, "Unknown resource type for 5QI %u\n", five_qi);
break;
}
}
@@ -470,13 +534,16 @@ int nr_rrc_find_suitable_drb_for_qos(gNB_RRC_UE_t *UE,
const pdusession_level_qos_parameter_t *qos_params,
const seq_arr_t *flows)
{
const uint8_t five_qi = qos_params->fiveQI;
DevAssert(five_qi <= MAX_FIVEQI);
const uint16_t five_qi = get_qos_fiveqi(qos_params);
if (five_qi == NO_FIVEQI || !is_5qi_standardized(five_qi)) {
DevAssert(qos_params->fiveQI_type == DYNAMIC);
return -1; // Dynamic 5QI without standardized numeric value: no table-based classification.
}
qos_resource_type_t type = five_qi_resource_type[five_qi];
/* Dedicated flows (incl. DC-GBR 5QI 82-90): never reuse an existing DRB. Per-DRB DC-GBR
* multiplexing limit is one dedicated DRB each. */
if (nr_rrc_qos_dedicated_drb(qos_params->fiveQI) || type == QOS_RESOURCE_TYPE_DC_GBR) {
if (nr_rrc_qos_dedicated_drb(qos_params) || type == QOS_RESOURCE_TYPE_DC_GBR) {
return -1;
}
@@ -500,7 +567,7 @@ int nr_rrc_find_suitable_drb_for_qos(gNB_RRC_UE_t *UE,
FOR_EACH_SEQ_ARR(nr_rrc_qos_t *, existing_qos, flows) {
if (existing_qos->drb_id != drb->drb_id)
continue;
if (nr_rrc_qos_dedicated_drb(existing_qos->qos.fiveQI)) {
if (nr_rrc_qos_dedicated_drb(&existing_qos->qos)) {
has_dedicated_flow_on_drb = true;
break;
}
@@ -521,9 +588,9 @@ int nr_rrc_find_suitable_drb_for_qos(gNB_RRC_UE_t *UE,
if (has_capacity && total_flows_on_drb < MAX_QOS_FLOWS_PER_DRB_TOTAL) {
LOG_I(NR_RRC,
"Found suitable DRB %d to multiplex 5QI %ld (QFI %d) - current flows: GBR=%d, Non-GBR=%d\n",
"Found suitable DRB %d to multiplex 5QI %d (QFI %d) - current flows: GBR=%d, Non-GBR=%d\n",
drb->drb_id,
qos_params->fiveQI,
five_qi,
qos_params->qfi,
gbr_count,
non_gbr_count);
@@ -550,6 +617,8 @@ bool nr_rrc_assign_drb_to_qos_flow(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const pd
DevAssert(session);
DevAssert(qos);
const uint16_t five_qi = get_qos_fiveqi(&qos->qos);
// First, try to find an existing suitable DRB
int drb_id = nr_rrc_find_suitable_drb_for_qos(UE, session->pdusession_id, &qos->qos, &session->qos);
@@ -562,7 +631,7 @@ bool nr_rrc_assign_drb_to_qos_flow(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const pd
// Store the assigned DRB ID
drb_id = rrc_drb->drb_id;
is_new_drb = true;
LOG_I(NR_RRC, "UE %d: created new DRB %d for QFI %d (5QI %lu)\n", UE->rrc_ue_id, drb_id, qos->qos.qfi, qos->qos.fiveQI);
LOG_I(NR_RRC, "UE %d: created new DRB %d for QFI %d (5QI %d)\n", UE->rrc_ue_id, drb_id, qos->qos.qfi, five_qi);
}
// Assign the DRB ID to the QoS flow
@@ -570,11 +639,11 @@ bool nr_rrc_assign_drb_to_qos_flow(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, const pd
qos->drb_id = drb_id;
LOG_I(NR_RRC,
"UE %d: assigned DRB %d to QFI %d (5QI %lu) in PDU session %d\n",
"UE %d: assigned DRB %d to QFI %d (5QI %d) in PDU session %d\n",
UE->rrc_ue_id,
drb_id,
qos->qos.qfi,
qos->qos.fiveQI,
five_qi,
session->pdusession_id);
return is_new_drb;
@@ -599,8 +668,11 @@ void nr_rrc_add_bearers(gNB_RRC_INST *rrc, gNB_RRC_UE_t *UE, int n, pdusession_t
nr_rrc_assign_drb_to_qos_flow(rrc, UE, session, qos);
/* TS 23.501 §5.7.2.7: the QoS Flow associated with the default QoS rule should be a Non-GBR QoS Flow
* from the standardized value range. Therefore, we prefer the DRB mapped from the first Non-GBR QoS flow here. */
DevAssert(qos->qos.fiveQI <= MAX_FIVEQI);
if (qos->qos.fiveQI_type == NON_DYNAMIC && nr_rrc_is_non_gbr_fiveqi(qos->qos.fiveQI) && !default_set) {
const uint16_t five_qi = get_qos_fiveqi(&qos->qos);
if (five_qi == NO_FIVEQI)
continue; // Dynamic 5QI: not eligible for default QoS rule selection here.
DevAssert(five_qi <= MAX_FIVEQI);
if (qos->qos.fiveQI_type == NON_DYNAMIC && nr_rrc_is_non_gbr_fiveqi(five_qi) && !default_set) {
session->sdap_config.default_drb = qos->drb_id;
default_set = true;
}

View File

@@ -126,7 +126,14 @@ static void test_rrc_qos(void)
LOG_A(NR_RRC, "Created PDU Session %d for QoS test\n", session_id);
const int qfi = 4;
const pdusession_level_qos_parameter_t param = { .fiveQI = 9, .fiveQI_type = NON_DYNAMIC, .qfi = qfi };
const pdusession_level_qos_parameter_t param = {
.qfi = qfi,
.fiveQI_type = NON_DYNAMIC,
.qos_characteristics.non_dynamic =
{
.fiveQI = 9,
},
};
LOG_A(NR_RRC, "Adding QoS flow with QFI %d\n", qfi);
nr_rrc_qos_t *added = add_qos(&in.qos, &param);
AssertFatal(added, "add_qos failed");
@@ -136,6 +143,30 @@ static void test_rrc_qos(void)
AssertFatal(found && found == added, "find_qos failed");
LOG_A(NR_RRC, "QoS flow find test passed\n");
const int dyn_qfi = 5;
uint16_t dyn_fiveqi = 7;
const pdusession_level_qos_parameter_t dyn_param = {
.qfi = dyn_qfi,
.fiveQI_type = DYNAMIC,
.qos_characteristics.dynamic =
{
.fiveQI = &dyn_fiveqi,
.qos_priority = 10,
.packet_delay_budget = 20,
.per = {.scalar = 1, .exponent = 6},
},
};
LOG_A(NR_RRC, "Adding Dynamic QoS flow with QFI %d\n", dyn_qfi);
nr_rrc_qos_t *dyn_added = add_qos(&in.qos, &dyn_param);
AssertFatal(dyn_added, "add_qos (dynamic) failed");
nr_rrc_qos_t *dyn_found = find_qos(&in.qos, dyn_qfi);
AssertFatal(dyn_found && dyn_found == dyn_added, "find_qos (dynamic) failed");
AssertFatal(dyn_found->qos.fiveQI_type == DYNAMIC, "dynamic flow type mismatch");
AssertFatal(dyn_found->qos.qos_characteristics.dynamic.fiveQI != NULL, "dynamic fiveQI missing");
AssertFatal(*dyn_found->qos.qos_characteristics.dynamic.fiveQI == dyn_fiveqi, "dynamic fiveQI mismatch");
LOG_A(NR_RRC, "Dynamic QoS flow add/find test passed\n");
seq_arr_free(&pduSessions, free_pdusession);
}

View File

@@ -190,12 +190,40 @@ pdusession_level_qos_parameter_t fill_qos(uint8_t qfi, const NGAP_QosFlowLevelQo
AssertFatal(qosChar != NULL, "QoS characteristics are NULL\n");
if (qosChar->present == NGAP_QosCharacteristics_PR_nonDynamic5QI) {
AssertFatal(qosChar->choice.nonDynamic5QI != NULL, "nonDynamic5QI is NULL\n");
const NGAP_NonDynamic5QIDescriptor_t *nonDyn = qosChar->choice.nonDynamic5QI;
non_dynamic_5qi_t *out_non_dyn = &out.qos_characteristics.non_dynamic;
out.fiveQI_type = NON_DYNAMIC;
out.fiveQI = qosChar->choice.nonDynamic5QI->fiveQI;
/** For NonDynamic5QI, fiveQI is mandatory and indicates standardized or pre-configured 5QI
* (5G QoS characteristics are not signaled, they are derived from the 5QI value) */
out_non_dyn->fiveQI = nonDyn->fiveQI;
// Extract priorityLevelQos if present (optional for NonDynamic5QI)
if (nonDyn->priorityLevelQos != NULL) {
out_non_dyn->qos_priority = calloc_or_fail(1, sizeof(*out_non_dyn->qos_priority));
*out_non_dyn->qos_priority = *nonDyn->priorityLevelQos;
DevAssert(*out_non_dyn->qos_priority >= MIN_QOS_PRIORITY_LEVEL && *out_non_dyn->qos_priority <= MAX_QOS_PRIORITY_LEVEL);
}
} else if (qosChar->present == NGAP_QosCharacteristics_PR_dynamic5QI) {
AssertFatal(qosChar->choice.dynamic5QI != NULL, "dynamic5QI is NULL\n");
const NGAP_Dynamic5QIDescriptor_t *dyn = qosChar->choice.dynamic5QI;
dynamic_5qi_t *out_dyn = &out.qos_characteristics.dynamic;
out.fiveQI_type = DYNAMIC;
out.fiveQI = *qosChar->choice.dynamic5QI->fiveQI;
/** Extract fiveQI if present (optional for Dynamic5QI)
* For Dynamic5QI, fiveQI is optional and indicates dynamically assigned 5QI
* (5G QoS characteristics are signaled as part of the QoS profile) */
if (dyn->fiveQI != NULL) {
out_dyn->fiveQI = calloc_or_fail(1, sizeof(*out_dyn->fiveQI));
*out_dyn->fiveQI = *dyn->fiveQI;
}
// Extract priorityLevelQos (mandatory)
out_dyn->qos_priority = dyn->priorityLevelQos;
DevAssert(out_dyn->qos_priority >= MIN_QOS_PRIORITY_LEVEL && out_dyn->qos_priority <= MAX_QOS_PRIORITY_LEVEL);
/** Extract packetDelayBudget (mandatory)
* Note: Per 3GPP TS 38.413 §9.3.1.18, this IE is ignored if Extended Packet Delay Budget
* is present in iE_Extensions. Extended Packet Delay Budget parsing is not yet implemented. */
out_dyn->packet_delay_budget = dyn->packetDelayBudget;
// Extract packetErrorRate (mandatory)
out_dyn->per.scalar = dyn->packetErrorRate.pERScalar;
out_dyn->per.exponent = dyn->packetErrorRate.pERExponent;
} else {
AssertFatal(0, "Unsupported QoS Characteristics present value: %d\n", qosChar->present);
}