Commit bc9f95c9 authored by Robert Schmidt's avatar Robert Schmidt

Merge remote-tracking branch 'origin/fixes-gtp-f1' into integration_2024_w11

parents b6aefac5 73d21d6d
......@@ -38,31 +38,153 @@
#include "rrc_extern.h"
#include "openair2/RRC/NR/rrc_gNB_NGAP.h"
#include <openair3/ocp-gtpu/gtp_itf.h>
#include "LAYER2/nr_pdcp/nr_pdcp_oai_api.h"
static void setQos(F1AP_NonDynamic5QIDescriptor_t **toFill)
static void f1ap_write_drb_qos_param(const f1ap_qos_flow_level_qos_parameters_t *drb_qos_in, F1AP_QoSFlowLevelQoSParameters_t *asn1_qosparam)
{
asn1cCalloc(*toFill, tmp);
/* fiveQI */
tmp->fiveQI = 1L;
int type = drb_qos_in->qos_characteristics.qos_type;
const f1ap_qos_characteristics_t *drb_qos_char_in = &drb_qos_in->qos_characteristics;
if (type == non_dynamic) {
asn1_qosparam->qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
asn1cCalloc(asn1_qosparam->qoS_Characteristics.choice.non_Dynamic_5QI, tmp);
/* 5QI */
tmp->fiveQI = drb_qos_char_in->non_dynamic.fiveqi;
} else {
asn1_qosparam->qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_dynamic_5QI;
asn1cCalloc(asn1_qosparam->qoS_Characteristics.choice.dynamic_5QI, tmp);
/* qoSPriorityLevel */
tmp->qoSPriorityLevel = drb_qos_char_in->dynamic.qos_priority_level;
/* packetDelayBudget */
tmp->packetDelayBudget = drb_qos_char_in->dynamic.packet_delay_budget;
/* packetErrorRate */
tmp->packetErrorRate.pER_Scalar = drb_qos_char_in->dynamic.packet_error_rate.per_scalar;
tmp->packetErrorRate.pER_Exponent = drb_qos_char_in->dynamic.packet_error_rate.per_scalar;
/* OPTIONAL delayCritical */
// asn1cCallocOne(asn1_qosparam->qoS_Characteristics.choice.dynamic_5QI->delayCritical, 1L);
/* OPTIONAL averagingWindow */
// asn1cCallocOne(asn1_qosparam->qoS_Characteristics.choice.dynamic_5QI->averagingWindow, 1L);
/* OPTIONAL maxDataBurstVolume */
// asn1cCallocOne(asn1_qosparam->qoS_Characteristics.choice.dynamic_5QI->maxDataBurstVolume, 1L);
}
{
asn1_qosparam->nGRANallocationRetentionPriority.priorityLevel = drb_qos_in->alloc_reten_priority.priority_level;
asn1_qosparam->nGRANallocationRetentionPriority.pre_emptionCapability = drb_qos_in->alloc_reten_priority.preemption_capability;
asn1_qosparam->nGRANallocationRetentionPriority.pre_emptionVulnerability =
drb_qos_in->alloc_reten_priority.preemption_vulnerability;
} // nGRANallocationRetentionPriority
/* OPTIONAL */
/* qoSPriorityLevel */
/* gBR_QoS_Flow_Information */
if (0) {
asn1cCallocOne((*toFill)->qoSPriorityLevel, 1L);
asn1cCalloc(asn1_qosparam->gBR_QoS_Flow_Information, tmp);
asn_long2INTEGER(&tmp->maxFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->maxFlowBitRateUplink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateUplink, 1L);
/* OPTIONAL */
/* maxPacketLossRateDownlink */
// asn1cCallocOne(asn1_qosparam->gBR_QoS_Flow_Information->maxPacketLossRateDownlink, 1L);
/* OPTIONAL */
/* maxPacketLossRateUplink */
//asn1cCallocOne(asn1_qosparam->gBR_QoS_Flow_Information->maxPacketLossRateUplink, 1L);
}
/* OPTIONAL */
/* averagingWindow */
/* reflective_QoS_Attribute */
if (0) {
asn1cCallocOne((*toFill)->averagingWindow, 1L);
asn1cCallocOne(asn1_qosparam->reflective_QoS_Attribute, 1L);
}
}
static void f1ap_write_drb_nssai(const nssai_t *nssai, F1AP_SNSSAI_t *asn1_nssai)
{
OCTET_STRING_fromBuf(&asn1_nssai->sST, (char *)&nssai->sst, 1);
/* OPTIONAL */
/* maxDataBurstVolume */
if (0) {
asn1cCallocOne((*toFill)->maxDataBurstVolume, 1L);
if (nssai->sd != 0xffffff)
OCTET_STRING_fromBuf(asn1_nssai->sD, (char *)&nssai->sd, 3);
}
static void f1ap_write_flows_mapped(const f1ap_flows_mapped_to_drb_t *flows_mapped, F1AP_Flows_Mapped_To_DRB_List_t *asn1_flows_mapped, int n)
{
for (int k = 0; k < n; k++) {
asn1cSequenceAdd(asn1_flows_mapped->list, F1AP_Flows_Mapped_To_DRB_Item_t, flow_item);
const f1ap_flows_mapped_to_drb_t *qos_flow_in = flows_mapped + k;
/* qoSFlowIndicator */
flow_item->qoSFlowIdentifier = qos_flow_in->qfi;
/* qoSFlowLevelQoSParameters */
const f1ap_qos_flow_level_qos_parameters_t *flow_qos_params_in = &qos_flow_in->qos_params;
/* qoS_Characteristics */
F1AP_QoS_Characteristics_t *QosParams = &flow_item->qoSFlowLevelQoSParameters.qoS_Characteristics;
const f1ap_qos_characteristics_t *flow_qos_char_in = &flow_qos_params_in->qos_characteristics;
int type = flow_qos_params_in->qos_characteristics.qos_type;
if (type == non_dynamic) {
QosParams->present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
asn1cCalloc(QosParams->choice.non_Dynamic_5QI, tmp);
/* 5QI */
tmp->fiveQI = flow_qos_char_in->non_dynamic.fiveqi;
} else {
QosParams->present = F1AP_QoS_Characteristics_PR_dynamic_5QI;
asn1cCalloc(QosParams->choice.dynamic_5QI, tmp);
/* qoSPriorityLevel */
tmp->qoSPriorityLevel = flow_qos_char_in->dynamic.qos_priority_level;
/* packetDelayBudget */
tmp->packetDelayBudget = flow_qos_char_in->dynamic.packet_delay_budget;
/* packetErrorRate */
tmp->packetErrorRate.pER_Scalar = flow_qos_char_in->dynamic.packet_error_rate.per_scalar;
tmp->packetErrorRate.pER_Exponent = flow_qos_char_in->dynamic.packet_error_rate.per_exponent;
/* OPTIONAL delayCritical */
//asn1cCallocOne(QosParams->choice.dynamic_5QI->delayCritical, 1);
/* OPTIONAL averagingWindow */
//asn1cCallocOne(QosParams->choice.dynamic_5QI->averagingWindow, 1);
/* OPTIONAL maxDataBurstVolume */
//asn1cCallocOne(QosParams->choice.dynamic_5QI->maxDataBurstVolume, 1);
}
/* nGRANallocationRetentionPriority */
{
flow_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.priorityLevel =
flow_qos_params_in->alloc_reten_priority.priority_level;
flow_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.pre_emptionCapability =
flow_qos_params_in->alloc_reten_priority.preemption_capability;
flow_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.pre_emptionVulnerability =
flow_qos_params_in->alloc_reten_priority.preemption_vulnerability;
} // nGRANallocationRetentionPriority
/* OPTIONAL */
/* gBR_QoS_Flow_Information */
if (0) {
asn1cCalloc(flow_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information, tmp);
asn_long2INTEGER(&tmp->maxFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->maxFlowBitRateUplink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateUplink, 1L);
/* OPTIONAL maxPacketLossRateDownlink */
//asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information->maxPacketLossRateDownlink, 1L);
/* OPTIONAL maxPacketLossRateUplink */
//asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information->maxPacketLossRateUplink, 1L);
}
/* OPTIONAL reflective_QoS_Attribute */
//asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.reflective_QoS_Attribute, 1L);
}
}
......@@ -275,7 +397,7 @@ int CU_send_UE_CONTEXT_SETUP_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_context_setu
ie12->value.present = F1AP_UEContextSetupRequestIEs__value_PR_DRBs_ToBeSetup_List;
for (int i = 0; i < f1ap_ue_context_setup_req->drbs_to_be_setup_length; i++) {
//
const f1ap_drb_to_be_setup_t *drb = &f1ap_ue_context_setup_req->drbs_to_be_setup[i];
asn1cSequenceAdd(ie12->value.choice.DRBs_ToBeSetup_List.list, F1AP_DRBs_ToBeSetup_ItemIEs_t, drbs_toBeSetup_item_ies);
drbs_toBeSetup_item_ies->id = F1AP_ProtocolIE_ID_id_DRBs_ToBeSetup_Item;
drbs_toBeSetup_item_ies->criticality = F1AP_Criticality_reject;
......@@ -283,7 +405,7 @@ int CU_send_UE_CONTEXT_SETUP_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_context_setu
/* 12.1 DRBs_ToBeSetup_Item */
F1AP_DRBs_ToBeSetup_Item_t *drbs_toBeSetup_item=&drbs_toBeSetup_item_ies->value.choice.DRBs_ToBeSetup_Item;
/* 12.1.1 dRBID */
drbs_toBeSetup_item->dRBID = f1ap_ue_context_setup_req->drbs_to_be_setup[i].drb_id; // 9
drbs_toBeSetup_item->dRBID = drb->drb_id;
/* 12.1.2 qoSInformation */
int some_decide_qos = 0; // BK: Need Check
......@@ -314,6 +436,7 @@ int CU_send_UE_CONTEXT_SETUP_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_context_setu
}
} else {
/* 12.1.2 DRB_Information */
const f1ap_drb_information_t *drb_info = &drb->drb_info;
drbs_toBeSetup_item->qoSInformation.present = F1AP_QoSInformation_PR_choice_extension;
F1AP_QoSInformation_ExtIEs_t *ie = (F1AP_QoSInformation_ExtIEs_t *)calloc(1, sizeof(*ie));
ie->id = F1AP_ProtocolIE_ID_id_DRB_Information;
......@@ -321,90 +444,12 @@ int CU_send_UE_CONTEXT_SETUP_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_context_setu
ie->value.present = F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information;
F1AP_DRB_Information_t *DRB_Information = &ie->value.choice.DRB_Information;
drbs_toBeSetup_item->qoSInformation.choice.choice_extension = (struct F1AP_ProtocolIE_SingleContainer *)ie;
/* 12.1.2.1 dRB_QoS */
{
/* qoS_Characteristics */
{
int some_decide_qoS_characteristics = 0; // BK: Need Check
if (some_decide_qoS_characteristics) {
DRB_Information->dRB_QoS.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
setQos(&DRB_Information->dRB_QoS.qoS_Characteristics.choice.non_Dynamic_5QI);
} else {
DRB_Information->dRB_QoS.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_dynamic_5QI;
asn1cCalloc(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI, tmp);
/* qoSPriorityLevel */
tmp->qoSPriorityLevel = 1L;
/* packetDelayBudget */
tmp->packetDelayBudget = 1L;
/* packetErrorRate */
tmp->packetErrorRate.pER_Scalar = 1L;
tmp->packetErrorRate.pER_Exponent = 6L;
/* OPTIONAL */
/* delayCritical */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI->delayCritical, 1L);
}
/* OPTIONAL */
/* averagingWindow */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI->averagingWindow, 1L);
}
/* OPTIONAL */
/* maxDataBurstVolume */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI->maxDataBurstVolume, 1L);
}
} // if some_decide_qoS_characteristics
} // qoS_Characteristics
/* nGRANallocationRetentionPriority */
{
DRB_Information->dRB_QoS.nGRANallocationRetentionPriority.priorityLevel = F1AP_PriorityLevel_highest; // enum
DRB_Information->dRB_QoS.nGRANallocationRetentionPriority.pre_emptionCapability = F1AP_Pre_emptionCapability_shall_not_trigger_pre_emption; // enum
DRB_Information->dRB_QoS.nGRANallocationRetentionPriority.pre_emptionVulnerability = F1AP_Pre_emptionVulnerability_not_pre_emptable; // enum
} // nGRANallocationRetentionPriority
/* OPTIONAL */
/* gBR_QoS_Flow_Information */
if (0) {
asn1cCalloc(DRB_Information->dRB_QoS.gBR_QoS_Flow_Information, tmp);
asn_long2INTEGER(&tmp->maxFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->maxFlowBitRateUplink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateUplink, 1L);
/* OPTIONAL */
/* maxPacketLossRateDownlink */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.gBR_QoS_Flow_Information->maxPacketLossRateDownlink, 1L);
}
/* OPTIONAL */
/* maxPacketLossRateUplink */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.gBR_QoS_Flow_Information->maxPacketLossRateUplink, 1L);
}
}
/* OPTIONAL */
/* reflective_QoS_Attribute */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.reflective_QoS_Attribute, 1L);
}
} // dRB_QoS
/* 12.1.2.2 sNSSAI */
{
/* sST */
OCTET_STRING_fromBuf(&DRB_Information->sNSSAI.sST, (char *)&f1ap_ue_context_setup_req->drbs_to_be_setup[i].nssai.sst, 1);
f1ap_write_drb_qos_param(&drb_info->drb_qos, &DRB_Information->dRB_QoS);
/* OPTIONAL */
const uint32_t sd = (f1ap_ue_context_setup_req->drbs_to_be_setup[i].nssai.sd & 0xffffff);
if (sd != 0xffffff)
OCTET_STRING_fromBuf(DRB_Information->sNSSAI.sD, (char *)&sd, 3);
}
/* 12.1.2.2 sNSSAI */
f1ap_write_drb_nssai(&drb->nssai, &DRB_Information->sNSSAI);
/* OPTIONAL */
/* 12.1.2.3 notificationControl */
......@@ -414,108 +459,12 @@ int CU_send_UE_CONTEXT_SETUP_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_context_setu
}
/* 12.1.2.4 flows_Mapped_To_DRB_List */ // BK: need verifiy
for (int k = 0; k < 1; k ++) {
asn1cSequenceAdd(DRB_Information->flows_Mapped_To_DRB_List.list,
F1AP_Flows_Mapped_To_DRB_Item_t, flows_mapped_to_drb_item);
/* qoSFlowIndicator */
flows_mapped_to_drb_item->qoSFlowIdentifier = 1L;
/* qoSFlowLevelQoSParameters */
{
/* qoS_Characteristics */
{
int some_decide_qoS_characteristics = 0; // BK: Need Check
F1AP_QoS_Characteristics_t *QosParams=&flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.qoS_Characteristics;
if (some_decide_qoS_characteristics) {
QosParams->present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
setQos(&QosParams->choice.non_Dynamic_5QI);
} else {
QosParams->present = F1AP_QoS_Characteristics_PR_dynamic_5QI;
asn1cCalloc(QosParams->choice.dynamic_5QI, tmp);
/* qoSPriorityLevel */
tmp->qoSPriorityLevel = 1L;
/* packetDelayBudget */
tmp->packetDelayBudget = 1L;
/* packetErrorRate */
tmp->packetErrorRate.pER_Scalar = 1L;
tmp->packetErrorRate.pER_Exponent = 6L;
/* OPTIONAL */
/* delayCritical */
if (0) {
asn1cCalloc(QosParams->choice.dynamic_5QI->delayCritical, tmp);
*tmp = 1L;
}
/* OPTIONAL */
/* averagingWindow */
if (0) {
asn1cCalloc(QosParams->choice.dynamic_5QI->averagingWindow, tmp);
*tmp = 1L;
}
/* OPTIONAL */
/* maxDataBurstVolume */
if (0) {
asn1cCalloc(QosParams->choice.dynamic_5QI->maxDataBurstVolume, tmp);
*tmp= 1L;
}
} // if some_decide_qoS_characteristics
} // qoS_Characteristics
/* nGRANallocationRetentionPriority */
{
flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.priorityLevel = F1AP_PriorityLevel_highest; // enum
flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.pre_emptionCapability = F1AP_Pre_emptionCapability_shall_not_trigger_pre_emption; // enum
flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.pre_emptionVulnerability = F1AP_Pre_emptionVulnerability_not_pre_emptable; // enum
} // nGRANallocationRetentionPriority
/* OPTIONAL */
/* gBR_QoS_Flow_Information */
if (0) {
asn1cCalloc(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information, tmp);
asn_long2INTEGER(&tmp->maxFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->maxFlowBitRateUplink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateUplink, 1L);
/* OPTIONAL */
/* maxPacketLossRateDownlink */
if (0) {
asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information->maxPacketLossRateDownlink, 1L);
}
/* OPTIONAL */
/* maxPacketLossRateUplink */
if (0) {
asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information->maxPacketLossRateUplink, 1L);
}
}
/* OPTIONAL */
/* reflective_QoS_Attribute */
if (0) {
asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.reflective_QoS_Attribute, 1L);
}
} // qoSFlowLevelQoSParameters
}
f1ap_write_flows_mapped(drb_info->flows_mapped_to_drb, &DRB_Information->flows_Mapped_To_DRB_List, drb_info->flows_to_be_setup_length);
} // if some_decide_qos
/* 12.1.3 uLUPTNLInformation_ToBeSetup_List */
for (int j = 0; j < f1ap_ue_context_setup_req->drbs_to_be_setup[i].up_ul_tnl_length; j++) {
/*Use a dummy teid for the outgoing GTP-U tunnel (DU) which will be updated once we get the UE context setup response from the DU*/
/* Use a dummy address and teid for the outgoing GTP-U tunnel (DU) which will be updated once we get the UE context setup response from the DU */
transport_layer_addr_t addr = { .length= 32, .buffer= { 0 } };
f1ap_ue_context_setup_req->drbs_to_be_setup[i].up_ul_tnl[j].teid = newGtpuCreateTunnel(getCxt(0)->gtpInst,
f1ap_ue_context_setup_req->gNB_CU_ue_id,
f1ap_ue_context_setup_req->drbs_to_be_setup[i].drb_id,
f1ap_ue_context_setup_req->drbs_to_be_setup[i].drb_id,
0xFFFF, // We will set the right value from DU answer
-1, // no qfi
addr, // We will set the right value from DU answer
f1ap_ue_context_setup_req->drbs_to_be_setup[i].up_dl_tnl[0].port,
cu_f1u_data_req,
NULL);
DevAssert(f1ap_ue_context_setup_req->drbs_to_be_setup[i].up_ul_tnl[j].teid > 0);
/* 12.3.1 ULTunnels_ToBeSetup_Item */
asn1cSequenceAdd(drbs_toBeSetup_item->uLUPTNLInformation_ToBeSetup_List.list,
F1AP_ULUPTNLInformation_ToBeSetup_Item_t, uLUPTNLInformation_ToBeSetup_Item);
......@@ -688,11 +637,6 @@ int CU_handle_UE_CONTEXT_SETUP_RESPONSE(instance_t instance, sctp_assoc_t assoc_
F1AP_GTPTunnel_t *dl_up_tnl0 = dl_up_tnl_info_p->dLUPTNLInformation.choice.gTPTunnel;
BIT_STRING_TO_TRANSPORT_LAYER_ADDRESS_IPv4(&dl_up_tnl0->transportLayerAddress, drb_p->up_dl_tnl[0].tl_address);
OCTET_STRING_TO_UINT32(&dl_up_tnl0->gTP_TEID, drb_p->up_dl_tnl[0].teid);
GtpuUpdateTunnelOutgoingAddressAndTeid(getCxt(instance)->gtpInst,
f1ap_ue_context_setup_resp->gNB_DU_ue_id,
(ebi_t)drbs_setup_item_p->dRBID,
drb_p->up_dl_tnl[0].tl_address,
drb_p->up_dl_tnl[0].teid);
}
}
......@@ -1182,6 +1126,7 @@ int CU_send_UE_CONTEXT_MODIFICATION_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_conte
ie12->value.present = F1AP_UEContextModificationRequestIEs__value_PR_DRBs_ToBeSetupMod_List;
for (int i = 0; i < f1ap_ue_context_modification_req->drbs_to_be_setup_length; i++) {
const f1ap_drb_to_be_setup_t *drb = &f1ap_ue_context_modification_req->drbs_to_be_setup[i];
asn1cSequenceAdd(ie12->value.choice.DRBs_ToBeSetupMod_List.list,
F1AP_DRBs_ToBeSetupMod_ItemIEs_t, drbs_toBeSetupMod_item_ies);
drbs_toBeSetupMod_item_ies->id = F1AP_ProtocolIE_ID_id_DRBs_ToBeSetupMod_Item;
......@@ -1191,7 +1136,7 @@ int CU_send_UE_CONTEXT_MODIFICATION_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_conte
F1AP_DRBs_ToBeSetupMod_Item_t *drbs_toBeSetupMod_item=
&drbs_toBeSetupMod_item_ies->value.choice.DRBs_ToBeSetupMod_Item;
/* dRBID */
drbs_toBeSetupMod_item->dRBID = f1ap_ue_context_modification_req->drbs_to_be_setup[i].drb_id;
drbs_toBeSetupMod_item->dRBID = drb->drb_id;
/* qoSInformation */
if(f1ap_ue_context_modification_req->QoS_information_type == EUTRAN_QoS){
......@@ -1235,99 +1180,12 @@ int CU_send_UE_CONTEXT_MODIFICATION_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_conte
ie->value.present = F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information;
F1AP_DRB_Information_t *DRB_Information = &ie->value.choice.DRB_Information;
drbs_toBeSetupMod_item->qoSInformation.choice.choice_extension = (struct F1AP_ProtocolIE_SingleContainer *)ie;
/* 12.1.2.1 dRB_QoS */
{
/* qoS_Characteristics */
f1ap_qos_flow_level_qos_parameters_t *drb_qos_in = &drb_info_in->drb_qos;
{
int some_decide_qoS_characteristics = drb_qos_in->qos_characteristics.qos_type;
f1ap_qos_characteristics_t *drb_qos_char_in = &drb_qos_in->qos_characteristics;
if (some_decide_qoS_characteristics == non_dynamic) {
DRB_Information->dRB_QoS.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
asn1cCalloc(DRB_Information->dRB_QoS.qoS_Characteristics.choice.non_Dynamic_5QI, tmp);
/* 5QI */
tmp->fiveQI = drb_qos_char_in->non_dynamic.fiveqi;
} else {
DRB_Information->dRB_QoS.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_dynamic_5QI;
asn1cCalloc(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI, tmp);
/* qoSPriorityLevel */
tmp->qoSPriorityLevel = drb_qos_char_in->dynamic.qos_priority_level;
/* packetDelayBudget */
tmp->packetDelayBudget = drb_qos_char_in->dynamic.packet_delay_budget;
/* packetErrorRate */
tmp->packetErrorRate.pER_Scalar = drb_qos_char_in->dynamic.packet_error_rate.per_scalar;
tmp->packetErrorRate.pER_Exponent = drb_qos_char_in->dynamic.packet_error_rate.per_scalar;
/* OPTIONAL */
/* delayCritical */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI->delayCritical, 1L);
}
/* OPTIONAL */
/* averagingWindow */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI->averagingWindow, 1L);
}
/* OPTIONAL */
/* maxDataBurstVolume */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.qoS_Characteristics.choice.dynamic_5QI->maxDataBurstVolume, 1L);
}
} // if some_decide_qoS_characteristics
} // qoS_Characteristics
/* nGRANallocationRetentionPriority */
{
DRB_Information->dRB_QoS.nGRANallocationRetentionPriority.priorityLevel =
drb_qos_in->alloc_reten_priority.priority_level;
DRB_Information->dRB_QoS.nGRANallocationRetentionPriority.pre_emptionCapability =
drb_qos_in->alloc_reten_priority.preemption_capability;
DRB_Information->dRB_QoS.nGRANallocationRetentionPriority.pre_emptionVulnerability =
drb_qos_in->alloc_reten_priority.preemption_vulnerability;
} // nGRANallocationRetentionPriority
/* OPTIONAL */
/* gBR_QoS_Flow_Information */
if (0) {
asn1cCalloc(DRB_Information->dRB_QoS.gBR_QoS_Flow_Information, tmp);
asn_long2INTEGER(&tmp->maxFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->maxFlowBitRateUplink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateUplink, 1L);
/* OPTIONAL */
/* maxPacketLossRateDownlink */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.gBR_QoS_Flow_Information->maxPacketLossRateDownlink, 1L);
}
/* OPTIONAL */
/* maxPacketLossRateUplink */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.gBR_QoS_Flow_Information->maxPacketLossRateUplink, 1L);
}
}
/* OPTIONAL */
/* reflective_QoS_Attribute */
if (0) {
asn1cCallocOne(DRB_Information->dRB_QoS.reflective_QoS_Attribute, 1L);
}
} // dRB_QoS
/* 12.1.2.2 sNSSAI */
{
/* sST */
OCTET_STRING_fromBuf(&DRB_Information->sNSSAI.sST, (char *)&f1ap_ue_context_modification_req->drbs_to_be_setup[i].nssai.sst, 1);
f1ap_write_drb_qos_param(&drb_info_in->drb_qos, &DRB_Information->dRB_QoS);
/* OPTIONAL */
const uint32_t sd = (f1ap_ue_context_modification_req->drbs_to_be_setup[i].nssai.sd & 0xffffff);
if (sd != 0xffffff)
OCTET_STRING_fromBuf(DRB_Information->sNSSAI.sD, (char *)&sd, 3);
}
/* 12.1.2.2 sNSSAI */
f1ap_write_drb_nssai(&drb->nssai, &DRB_Information->sNSSAI);
/* OPTIONAL */
/* 12.1.2.3 notificationControl */
......@@ -1337,102 +1195,7 @@ int CU_send_UE_CONTEXT_MODIFICATION_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_conte
}
/* 12.1.2.4 flows_Mapped_To_DRB_List */
for (int k = 0; k < drb_info_in->flows_to_be_setup_length; k++) {
asn1cSequenceAdd(DRB_Information->flows_Mapped_To_DRB_List.list,
F1AP_Flows_Mapped_To_DRB_Item_t, flows_mapped_to_drb_item);
f1ap_flows_mapped_to_drb_t *qos_flow_in = drb_info_in->flows_mapped_to_drb + k;
/* qoSFlowIndicator */
flows_mapped_to_drb_item->qoSFlowIdentifier = qos_flow_in->qfi;
/* qoSFlowLevelQoSParameters */
{
f1ap_qos_flow_level_qos_parameters_t *flow_qos_params_in = &qos_flow_in->qos_params;
/* qoS_Characteristics */
{
int some_decide_qoS_characteristics = flow_qos_params_in->qos_characteristics.qos_type;
F1AP_QoS_Characteristics_t *QosParams = &flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.qoS_Characteristics;
f1ap_qos_characteristics_t *flow_qos_char_in = &flow_qos_params_in->qos_characteristics;
if (some_decide_qoS_characteristics == non_dynamic) {
QosParams->present = F1AP_QoS_Characteristics_PR_non_Dynamic_5QI;
asn1cCalloc(QosParams->choice.non_Dynamic_5QI, tmp);
/* 5QI */
tmp->fiveQI = flow_qos_char_in->non_dynamic.fiveqi;
} else {
QosParams->present = F1AP_QoS_Characteristics_PR_dynamic_5QI;
asn1cCalloc(QosParams->choice.dynamic_5QI, tmp);
/* qoSPriorityLevel */
tmp->qoSPriorityLevel = flow_qos_char_in->dynamic.qos_priority_level;
/* packetDelayBudget */
tmp->packetDelayBudget = flow_qos_char_in->dynamic.packet_delay_budget;
/* packetErrorRate */
tmp->packetErrorRate.pER_Scalar = flow_qos_char_in->dynamic.packet_error_rate.per_scalar;
tmp->packetErrorRate.pER_Exponent = flow_qos_char_in->dynamic.packet_error_rate.per_exponent;
/* OPTIONAL */
/* delayCritical */
if (0) {
asn1cCalloc(QosParams->choice.dynamic_5QI->delayCritical, tmp);
*tmp = 1L;
}
/* OPTIONAL */
/* averagingWindow */
if (0) {
asn1cCalloc(QosParams->choice.dynamic_5QI->averagingWindow, tmp);
*tmp = 1L;
}
/* OPTIONAL */
/* maxDataBurstVolume */
if (0) {
asn1cCalloc(QosParams->choice.dynamic_5QI->maxDataBurstVolume, tmp);
*tmp= 1L;
}
} // if some_decide_qoS_characteristics
} // qoS_Characteristics
/* nGRANallocationRetentionPriority */
{
flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.priorityLevel =
flow_qos_params_in->alloc_reten_priority.priority_level;
flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.pre_emptionCapability =
flow_qos_params_in->alloc_reten_priority.preemption_capability;
flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.nGRANallocationRetentionPriority.pre_emptionVulnerability =
flow_qos_params_in->alloc_reten_priority.preemption_vulnerability;
} // nGRANallocationRetentionPriority
/* OPTIONAL */
/* gBR_QoS_Flow_Information */
if (0) {
asn1cCalloc(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information, tmp);
asn_long2INTEGER(&tmp->maxFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->maxFlowBitRateUplink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateDownlink, 1L);
asn_long2INTEGER(&tmp->guaranteedFlowBitRateUplink, 1L);
/* OPTIONAL */
/* maxPacketLossRateDownlink */
if (0) {
asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information->maxPacketLossRateDownlink, 1L);
}
/* OPTIONAL */
/* maxPacketLossRateUplink */
if (0) {
asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.gBR_QoS_Flow_Information->maxPacketLossRateUplink, 1L);
}
}
/* OPTIONAL */
/* reflective_QoS_Attribute */
if (0) {
asn1cCallocOne(flows_mapped_to_drb_item->qoSFlowLevelQoSParameters.reflective_QoS_Attribute, 1L);
}
} // qoSFlowLevelQoSParameters
}
f1ap_write_flows_mapped(drb_info_in->flows_mapped_to_drb, &DRB_Information->flows_Mapped_To_DRB_List, drb_info_in->flows_to_be_setup_length);
} //QoS information
......@@ -1569,9 +1332,6 @@ int CU_send_UE_CONTEXT_MODIFICATION_REQUEST(sctp_assoc_t assoc_id, f1ap_ue_conte
&drbs_toBeReleased_item_ies->value.choice.DRBs_ToBeReleased_Item;
/* dRBID */
drbs_toBeReleased_item->dRBID = f1ap_ue_context_modification_req->drbs_to_be_released[i].rb_id;
newGtpuDeleteOneTunnel(getCxt(0)->gtpInst,
f1ap_ue_context_modification_req->gNB_CU_ue_id,
f1ap_ue_context_modification_req->drbs_to_be_released[i].rb_id);
}
}
......@@ -1644,11 +1404,6 @@ int CU_handle_UE_CONTEXT_MODIFICATION_RESPONSE(instance_t instance, sctp_assoc_t
F1AP_GTPTunnel_t *dl_up_tnl0 = dl_up_tnl_info_p->dLUPTNLInformation.choice.gTPTunnel;
BIT_STRING_TO_TRANSPORT_LAYER_ADDRESS_IPv4(&dl_up_tnl0->transportLayerAddress, drb_p->up_dl_tnl[0].tl_address);
OCTET_STRING_TO_UINT32(&dl_up_tnl0->gTP_TEID, drb_p->up_dl_tnl[0].teid);
GtpuUpdateTunnelOutgoingAddressAndTeid(getCxt(instance)->gtpInst,
f1ap_ue_context_modification_resp->gNB_CU_ue_id,
(ebi_t)drbs_setupmod_item_p->dRBID,
drb_p->up_dl_tnl[0].tl_address,
drb_p->up_dl_tnl[0].teid);
}
}
// SRBs_FailedToBeSetupMod_List
......
......@@ -50,7 +50,6 @@
#include "asn1_msg.h"
#include "intertask_interface.h"
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include <openair3/ocp-gtpu/gtp_itf.h>
#include "openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h"
......@@ -83,7 +82,6 @@ int DU_handle_DL_RRC_MESSAGE_TRANSFER(instance_t instance, sctp_assoc_t assoc_id
/* strange: it is not named OLD_GNB_DU_UE... */
old_gNB_DU_ue_id_stack = ie->value.choice.GNB_DU_UE_F1AP_ID_1;
old_gNB_DU_ue_id = &old_gNB_DU_ue_id_stack;
gtpv1u_update_ue_id(getCxt(instance)->gtpInst, old_gNB_DU_ue_id_stack, du_ue_f1ap_id);
}
/* mandatory */
......
......@@ -40,6 +40,16 @@
//Fixme: Uniq dirty DU instance, by global var, datamodel need better management
instance_t DUuniqInstance=0;
static instance_t du_create_gtpu_instance_to_cu(const f1ap_net_config_t *nc)
{
openAddr_t tmp = {0};
strncpy(tmp.originHost, nc->DU_f1_ip_address.ipv4_address, sizeof(tmp.originHost) - 1);
strncpy(tmp.destinationHost, nc->CU_f1_ip_address.ipv4_address, sizeof(tmp.destinationHost) - 1);
sprintf(tmp.originService, "%d", nc->DUport);
sprintf(tmp.destinationService, "%d", nc->CUport);
return gtpv1Init(tmp);
}
void du_task_send_sctp_association_req(instance_t instance, f1ap_net_config_t *nc)
{
DevAssert(nc != NULL);
......@@ -116,6 +126,10 @@ void *F1AP_DU_task(void *arg) {
f1ap_net_config_t *nc = &F1AP_DU_REGISTER_REQ(msg).net_config;
createF1inst(myInstance, msgSetup, nc);
du_task_send_sctp_association_req(myInstance, nc);
instance_t gtpInst = du_create_gtpu_instance_to_cu(nc);
AssertFatal(gtpInst != 0, "cannot create DU F1-U GTP module\n");
getCxt(myInstance)->gtpInst = gtpInst;
DUuniqInstance = gtpInst;
} break;
case F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE:
......
......@@ -37,25 +37,82 @@
#include "openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h"
#include "openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include <openair3/ocp-gtpu/gtp_itf.h>
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h"
bool DURecvCb(protocol_ctxt_t *ctxt_pP,
const srb_flag_t srb_flagP,
const rb_id_t rb_idP,
const mui_t muiP,
const confirm_t confirmP,
const sdu_size_t sdu_buffer_sizeP,
unsigned char *const sdu_buffer_pP,
const pdcp_transmission_mode_t modeP,
const uint32_t *sourceL2Id,
const uint32_t *destinationL2Id)
static void f1ap_read_drb_qos_param(const F1AP_QoSFlowLevelQoSParameters_t *asn1_qos, f1ap_qos_flow_level_qos_parameters_t *drb_qos)
{
// The buffer comes from the stack in gtp-u thread, we have a make a separate buffer to enqueue in a inter-thread message queue
uint8_t *sdu = malloc16(sdu_buffer_sizeP);
memcpy(sdu, sdu_buffer_pP, sdu_buffer_sizeP);
du_rlc_data_req(ctxt_pP, srb_flagP, false, rb_idP, muiP, confirmP, sdu_buffer_sizeP, sdu);
return true;
f1ap_qos_characteristics_t *drb_qos_char = &drb_qos->qos_characteristics;
const F1AP_QoS_Characteristics_t *dRB_QoS_Char = &asn1_qos->qoS_Characteristics;
if (dRB_QoS_Char->present == F1AP_QoS_Characteristics_PR_non_Dynamic_5QI) {
drb_qos_char->qos_type = non_dynamic;
drb_qos_char->non_dynamic.fiveqi = dRB_QoS_Char->choice.non_Dynamic_5QI->fiveQI;
drb_qos_char->non_dynamic.qos_priority_level = (dRB_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel != NULL)
? *dRB_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel
: -1;
} else {
drb_qos_char->qos_type = dynamic;
drb_qos_char->dynamic.fiveqi =
(dRB_QoS_Char->choice.dynamic_5QI->fiveQI != NULL) ? *dRB_QoS_Char->choice.dynamic_5QI->fiveQI : -1;
drb_qos_char->dynamic.qos_priority_level = dRB_QoS_Char->choice.dynamic_5QI->qoSPriorityLevel;
drb_qos_char->dynamic.packet_delay_budget = dRB_QoS_Char->choice.dynamic_5QI->packetDelayBudget;
drb_qos_char->dynamic.packet_error_rate.per_scalar = dRB_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Scalar;
drb_qos_char->dynamic.packet_error_rate.per_exponent = dRB_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Exponent;
}
/* nGRANallocationRetentionPriority */
drb_qos->alloc_reten_priority.priority_level = asn1_qos->nGRANallocationRetentionPriority.priorityLevel;
drb_qos->alloc_reten_priority.preemption_vulnerability = asn1_qos->nGRANallocationRetentionPriority.pre_emptionVulnerability;
drb_qos->alloc_reten_priority.preemption_capability = asn1_qos->nGRANallocationRetentionPriority.pre_emptionVulnerability;
}
static void f1ap_read_flows_mapped(const F1AP_Flows_Mapped_To_DRB_List_t *asn1_flows_mapped, f1ap_flows_mapped_to_drb_t *flows_mapped, int n)
{
for (int k = 0; k < n; k++) {
f1ap_flows_mapped_to_drb_t *flows_mapped_to_drb = flows_mapped + k;
const F1AP_Flows_Mapped_To_DRB_Item_t *flows_Mapped_To_Drb = asn1_flows_mapped->list.array[0] + k;
flows_mapped_to_drb->qfi = flows_Mapped_To_Drb->qoSFlowIdentifier;
/* QoS-Flow-Level-QoS-Parameters */
{
f1ap_qos_flow_level_qos_parameters_t *flow_qos = &flows_mapped_to_drb->qos_params;
const F1AP_QoSFlowLevelQoSParameters_t *Flow_QoS = &flows_Mapped_To_Drb->qoSFlowLevelQoSParameters;
/* QoS Characteristics*/
f1ap_qos_characteristics_t *flow_qos_char = &flow_qos->qos_characteristics;
const F1AP_QoS_Characteristics_t *Flow_QoS_Char = &Flow_QoS->qoS_Characteristics;
if (Flow_QoS_Char->present == F1AP_QoS_Characteristics_PR_non_Dynamic_5QI) {
flow_qos_char->qos_type = non_dynamic;
flow_qos_char->non_dynamic.fiveqi = Flow_QoS_Char->choice.non_Dynamic_5QI->fiveQI;
flow_qos_char->non_dynamic.qos_priority_level = (Flow_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel != NULL)
? *Flow_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel
: -1;
} else {
flow_qos_char->qos_type = dynamic;
flow_qos_char->dynamic.fiveqi =
(Flow_QoS_Char->choice.dynamic_5QI->fiveQI != NULL) ? *Flow_QoS_Char->choice.dynamic_5QI->fiveQI : -1;
flow_qos_char->dynamic.qos_priority_level = Flow_QoS_Char->choice.dynamic_5QI->qoSPriorityLevel;
flow_qos_char->dynamic.packet_delay_budget = Flow_QoS_Char->choice.dynamic_5QI->packetDelayBudget;
flow_qos_char->dynamic.packet_error_rate.per_scalar = Flow_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Scalar;
flow_qos_char->dynamic.packet_error_rate.per_exponent = Flow_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Exponent;
}
/* nGRANallocationRetentionPriority */
flow_qos->alloc_reten_priority.priority_level = Flow_QoS->nGRANallocationRetentionPriority.priorityLevel;
flow_qos->alloc_reten_priority.preemption_vulnerability = Flow_QoS->nGRANallocationRetentionPriority.pre_emptionVulnerability;
flow_qos->alloc_reten_priority.preemption_capability = Flow_QoS->nGRANallocationRetentionPriority.pre_emptionVulnerability;
}
}
}
static void f1ap_read_drb_nssai(const F1AP_SNSSAI_t *asn1_nssai, nssai_t *nssai)
{
OCTET_STRING_TO_INT8(&asn1_nssai->sST, nssai->sst);
nssai->sd = 0xffffff;
if (asn1_nssai->sD != NULL)
memcpy((uint8_t *)&nssai->sd, asn1_nssai->sD->buf, 3);
}
int DU_handle_UE_CONTEXT_SETUP_REQUEST(instance_t instance, sctp_assoc_t assoc_id, uint32_t stream, F1AP_F1AP_PDU_t *pdu)
......@@ -159,6 +216,36 @@ int DU_handle_UE_CONTEXT_SETUP_REQUEST(instance_t instance, sctp_assoc_t assoc_i
drb_p->rlc_mode = RLC_MODE_TM;
break;
}
if (drbs_tobesetup_item_p->qoSInformation.present == F1AP_QoSInformation_PR_eUTRANQoS) {
AssertFatal(false, "Decode of eUTRANQoS is not implemented yet");
} // EUTRAN QoS Information
else {
/* 12.1.2 DRB_Information */
if (drbs_tobesetup_item_p->qoSInformation.present == F1AP_QoSInformation_PR_choice_extension) {
F1AP_QoSInformation_ExtIEs_t *ie =
(F1AP_QoSInformation_ExtIEs_t *)drbs_tobesetup_item_p->qoSInformation.choice.choice_extension;
if (ie->id == F1AP_ProtocolIE_ID_id_DRB_Information && ie->criticality == F1AP_Criticality_reject
&& ie->value.present == F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information) {
const F1AP_DRB_Information_t *dRB_Info = &ie->value.choice.DRB_Information;
f1ap_drb_information_t *drb_info = &drb_p->drb_info;
/* QoS-Flow-Level-QoS-Parameters */
/* QoS Characteristics*/
f1ap_read_drb_qos_param(&dRB_Info->dRB_QoS, &drb_info->drb_qos);
// 12.1.2.4 flows_Mapped_To_DRB_List
drb_info->flows_to_be_setup_length = dRB_Info->flows_Mapped_To_DRB_List.list.count;
drb_info->flows_mapped_to_drb = calloc(drb_info->flows_to_be_setup_length, sizeof(f1ap_flows_mapped_to_drb_t));
AssertFatal(drb_info->flows_mapped_to_drb, "could not allocate memory for drb_p->drb_info.flows_mapped_to_drb\n");
f1ap_read_flows_mapped(&dRB_Info->flows_Mapped_To_DRB_List, drb_info->flows_mapped_to_drb, drb_info->flows_to_be_setup_length);
/* S-NSSAI */
f1ap_read_drb_nssai(&dRB_Info->sNSSAI, &drb_p->nssai);
}
}
}
}
}
......@@ -305,6 +392,7 @@ int DU_send_UE_CONTEXT_SETUP_RESPONSE(sctp_assoc_t assoc_id, f1ap_ue_context_set
ie7->criticality = F1AP_Criticality_ignore;
ie7->value.present = F1AP_UEContextSetupResponseIEs__value_PR_DRBs_Setup_List;
for (int i=0; i< resp->drbs_to_be_setup_length; i++) {
f1ap_drb_to_be_setup_t *drb = &resp->drbs_to_be_setup[i];
//
asn1cSequenceAdd(ie7->value.choice.DRBs_Setup_List.list,
F1AP_DRBs_Setup_ItemIEs_t, drbs_setup_item_ies);
......@@ -315,14 +403,17 @@ int DU_send_UE_CONTEXT_SETUP_RESPONSE(sctp_assoc_t assoc_id, f1ap_ue_context_set
/* ADD */
F1AP_DRBs_Setup_Item_t *drbs_setup_item=&drbs_setup_item_ies->value.choice.DRBs_Setup_Item;
/* dRBID */
drbs_setup_item->dRBID = resp->drbs_to_be_setup[i].drb_id;
drbs_setup_item->dRBID = drb->drb_id;
/* OPTIONAL */
/* lCID */
//drbs_setup_item.lCID = (F1AP_LCID_t *)calloc(1, sizeof(F1AP_LCID_t));
//drbs_setup_item.lCID = 1L;
for (int j=0; j<resp->drbs_to_be_setup[i].up_dl_tnl_length; j++) {
for (int j = 0; j < drb->up_dl_tnl_length; j++) {
const f1ap_up_tnl_t *tnl = &drb->up_dl_tnl[j];
DevAssert(tnl->teid > 0);
/* ADD */
asn1cSequenceAdd(drbs_setup_item->dLUPTNLInformation_ToBeSetup_List.list,
F1AP_DLUPTNLInformation_ToBeSetup_Item_t, dLUPTNLInformation_ToBeSetup_Item);
......@@ -330,12 +421,9 @@ int DU_send_UE_CONTEXT_SETUP_RESPONSE(sctp_assoc_t assoc_id, f1ap_ue_context_set
/* gTPTunnel */
asn1cCalloc(dLUPTNLInformation_ToBeSetup_Item->dLUPTNLInformation.choice.gTPTunnel,gTPTunnel);
/* transportLayerAddress */
struct sockaddr_in addr= {0};
inet_pton(AF_INET, getCxt(0)->net_config.DU_f1_ip_address.ipv4_address, &addr.sin_addr.s_addr);
TRANSPORT_LAYER_ADDRESS_IPv4_TO_BIT_STRING(addr.sin_addr.s_addr,
&gTPTunnel->transportLayerAddress);
TRANSPORT_LAYER_ADDRESS_IPv4_TO_BIT_STRING(tnl->tl_address, &gTPTunnel->transportLayerAddress);
/* gTP_TEID */
INT32_TO_OCTET_STRING(resp->drbs_to_be_setup[i].up_dl_tnl[j].teid, &gTPTunnel->gTP_TEID);
INT32_TO_OCTET_STRING(tnl->teid, &gTPTunnel->gTP_TEID);
} // for j
} // for i
......@@ -772,16 +860,6 @@ int DU_send_UE_CONTEXT_RELEASE_COMPLETE(sctp_assoc_t assoc_id, f1ap_ue_context_r
return 0;
}
static instance_t du_create_gtpu_instance_to_cu(char *CUaddr, uint16_t CUport, char *DUaddr, uint16_t DUport)
{
openAddr_t tmp = {0};
strncpy(tmp.originHost, DUaddr, sizeof(tmp.originHost)-1);
strncpy(tmp.destinationHost, CUaddr, sizeof(tmp.destinationHost)-1);
sprintf(tmp.originService, "%d", DUport);
sprintf(tmp.destinationService, "%d", CUport);
return gtpv1Init(tmp);
}
int DU_handle_UE_CONTEXT_MODIFICATION_REQUEST(instance_t instance, sctp_assoc_t assoc_id, uint32_t stream, F1AP_F1AP_PDU_t *pdu)
{
F1AP_UEContextModificationRequest_t *container;
......@@ -853,27 +931,6 @@ int DU_handle_UE_CONTEXT_MODIFICATION_REQUEST(instance_t instance, sctp_assoc_t
// 3GPP assumes GTP-U is on port 2152, but OAI is configurable
drb_p->up_ul_tnl[0].port = getCxt(instance)->net_config.CUport;
extern instance_t DUuniqInstance;
if (DUuniqInstance == 0) {
char gtp_tunnel_ip_address[32];
snprintf(gtp_tunnel_ip_address,
sizeof(gtp_tunnel_ip_address),
"%d.%d.%d.%d",
drb_p->up_ul_tnl[0].tl_address & 0xff,
(drb_p->up_ul_tnl[0].tl_address >> 8) & 0xff,
(drb_p->up_ul_tnl[0].tl_address >> 16) & 0xff,
(drb_p->up_ul_tnl[0].tl_address >> 24) & 0xff);
getCxt(instance)->gtpInst = du_create_gtpu_instance_to_cu(gtp_tunnel_ip_address,
getCxt(instance)->net_config.CUport,
getCxt(instance)->net_config.DU_f1_ip_address.ipv4_address,
getCxt(instance)->net_config.DUport);
AssertFatal(getCxt(instance)->gtpInst > 0, "Failed to create CU F1-U UDP listener");
// Fixme: fully inconsistent instances management
// dirty global var is a bad fix
extern instance_t legacyInstanceMapping;
legacyInstanceMapping = DUuniqInstance = getCxt(instance)->gtpInst;
}
switch (drbs_tobesetupmod_item_p->rLCMode) {
case F1AP_RLCMode_rlc_am:
drb_p->rlc_mode = RLC_MODE_AM;
......@@ -894,101 +951,22 @@ int DU_handle_UE_CONTEXT_MODIFICATION_REQUEST(instance_t instance, sctp_assoc_t
(F1AP_QoSInformation_ExtIEs_t *)drbs_tobesetupmod_item_p->qoSInformation.choice.choice_extension;
if (ie->id == F1AP_ProtocolIE_ID_id_DRB_Information && ie->criticality == F1AP_Criticality_reject
&& ie->value.present == F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information) {
F1AP_DRB_Information_t *dRB_Info = &ie->value.choice.DRB_Information;
f1ap_drb_information_t *drb_info = &f1ap_ue_context_modification_req->drbs_to_be_setup->drb_info;
/* 12.1.2.1 dRB_QoS */
{
/* QoS-Flow-Level-QoS-Parameters */
f1ap_qos_flow_level_qos_parameters_t *drb_qos = &drb_info->drb_qos;
F1AP_QoSFlowLevelQoSParameters_t *dRB_QoS = &dRB_Info->dRB_QoS;
{
/* QoS Characteristics*/
f1ap_qos_characteristics_t *drb_qos_char = &drb_qos->qos_characteristics;
F1AP_QoS_Characteristics_t *dRB_QoS_Char = &dRB_QoS->qoS_Characteristics;
if (dRB_QoS_Char->present == F1AP_QoS_Characteristics_PR_non_Dynamic_5QI) {
drb_qos_char->qos_type = non_dynamic;
drb_qos_char->non_dynamic.fiveqi = dRB_QoS_Char->choice.non_Dynamic_5QI->fiveQI;
drb_qos_char->non_dynamic.qos_priority_level = (dRB_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel != NULL)
? *dRB_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel
: -1;
} else {
drb_qos_char->qos_type = dynamic;
drb_qos_char->dynamic.fiveqi =
(dRB_QoS_Char->choice.dynamic_5QI->fiveQI != NULL) ? *dRB_QoS_Char->choice.dynamic_5QI->fiveQI : -1;
drb_qos_char->dynamic.qos_priority_level = dRB_QoS_Char->choice.dynamic_5QI->qoSPriorityLevel;
drb_qos_char->dynamic.packet_delay_budget = dRB_QoS_Char->choice.dynamic_5QI->packetDelayBudget;
drb_qos_char->dynamic.packet_error_rate.per_scalar = dRB_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Scalar;
drb_qos_char->dynamic.packet_error_rate.per_exponent =
dRB_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Exponent;
}
}
/* nGRANallocationRetentionPriority */
drb_qos->alloc_reten_priority.priority_level = dRB_QoS->nGRANallocationRetentionPriority.priorityLevel;
drb_qos->alloc_reten_priority.preemption_vulnerability =
dRB_QoS->nGRANallocationRetentionPriority.pre_emptionVulnerability;
drb_qos->alloc_reten_priority.preemption_capability =
dRB_QoS->nGRANallocationRetentionPriority.pre_emptionVulnerability;
} // dRB_QoS
const F1AP_DRB_Information_t *dRB_Info = &ie->value.choice.DRB_Information;
f1ap_drb_information_t *drb_info = &drb_p->drb_info;
/* QoS-Flow-Level-QoS-Parameters */
/* QoS Characteristics*/
f1ap_read_drb_qos_param(&dRB_Info->dRB_QoS, &drb_info->drb_qos);
// 12.1.2.4 flows_Mapped_To_DRB_List
drb_info->flows_to_be_setup_length = dRB_Info->flows_Mapped_To_DRB_List.list.count;
drb_info->flows_mapped_to_drb = calloc(drb_info->flows_to_be_setup_length, sizeof(f1ap_flows_mapped_to_drb_t));
AssertFatal(drb_info->flows_mapped_to_drb, "could not allocate memory for drb_p->drb_info.flows_mapped_to_drb\n");
for (int k = 0; k < drb_p->drb_info.flows_to_be_setup_length; k++) {
f1ap_flows_mapped_to_drb_t *flows_mapped_to_drb = drb_info->flows_mapped_to_drb + k;
F1AP_Flows_Mapped_To_DRB_Item_t *flows_Mapped_To_Drb = dRB_Info->flows_Mapped_To_DRB_List.list.array[0] + k;
flows_mapped_to_drb->qfi = flows_Mapped_To_Drb->qoSFlowIdentifier;
/* QoS-Flow-Level-QoS-Parameters */
{
f1ap_qos_flow_level_qos_parameters_t *flow_qos = &flows_mapped_to_drb->qos_params;
F1AP_QoSFlowLevelQoSParameters_t *Flow_QoS = &flows_Mapped_To_Drb->qoSFlowLevelQoSParameters;
/* QoS Characteristics*/
{
f1ap_qos_characteristics_t *flow_qos_char = &flow_qos->qos_characteristics;
F1AP_QoS_Characteristics_t *Flow_QoS_Char = &Flow_QoS->qoS_Characteristics;
if (Flow_QoS_Char->present == F1AP_QoS_Characteristics_PR_non_Dynamic_5QI) {
flow_qos_char->qos_type = non_dynamic;
flow_qos_char->non_dynamic.fiveqi = Flow_QoS_Char->choice.non_Dynamic_5QI->fiveQI;
flow_qos_char->non_dynamic.qos_priority_level =
(Flow_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel != NULL)
? *Flow_QoS_Char->choice.non_Dynamic_5QI->qoSPriorityLevel
: -1;
} else {
flow_qos_char->qos_type = dynamic;
flow_qos_char->dynamic.fiveqi =
(Flow_QoS_Char->choice.dynamic_5QI->fiveQI != NULL) ? *Flow_QoS_Char->choice.dynamic_5QI->fiveQI : -1;
flow_qos_char->dynamic.qos_priority_level = Flow_QoS_Char->choice.dynamic_5QI->qoSPriorityLevel;
flow_qos_char->dynamic.packet_delay_budget = Flow_QoS_Char->choice.dynamic_5QI->packetDelayBudget;
flow_qos_char->dynamic.packet_error_rate.per_scalar =
Flow_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Scalar;
flow_qos_char->dynamic.packet_error_rate.per_exponent =
Flow_QoS_Char->choice.dynamic_5QI->packetErrorRate.pER_Exponent;
}
}
/* nGRANallocationRetentionPriority */
flow_qos->alloc_reten_priority.priority_level = Flow_QoS->nGRANallocationRetentionPriority.priorityLevel;
flow_qos->alloc_reten_priority.preemption_vulnerability =
Flow_QoS->nGRANallocationRetentionPriority.pre_emptionVulnerability;
flow_qos->alloc_reten_priority.preemption_capability =
Flow_QoS->nGRANallocationRetentionPriority.pre_emptionVulnerability;
}
}
f1ap_read_flows_mapped(&dRB_Info->flows_Mapped_To_DRB_List, drb_info->flows_mapped_to_drb, drb_info->flows_to_be_setup_length);
/* S-NSSAI */
OCTET_STRING_TO_INT8(&dRB_Info->sNSSAI.sST, drb_p->nssai.sst);
if (dRB_Info->sNSSAI.sD != NULL)
memcpy((uint8_t *)&drb_p->nssai.sd, dRB_Info->sNSSAI.sD->buf, 3);
else
drb_p->nssai.sd = 0xffffff;
f1ap_read_drb_nssai(&dRB_Info->sNSSAI, &drb_p->nssai);
}
}
}
......@@ -1008,7 +986,6 @@ int DU_handle_UE_CONTEXT_MODIFICATION_REQUEST(instance_t instance, sctp_assoc_t
DevAssert(tbrel->id == F1AP_ProtocolIE_ID_id_DRBs_ToBeReleased_Item);
DevAssert(tbrel->value.present == F1AP_DRBs_ToBeReleased_ItemIEs__value_PR_DRBs_ToBeReleased_Item);
f1ap_ue_context_modification_req->drbs_to_be_released[i].rb_id = tbrel->value.choice.DRBs_ToBeReleased_Item.dRBID;
newGtpuDeleteOneTunnel(0, f1ap_ue_context_modification_req->gNB_DU_ue_id, f1ap_ue_context_modification_req->drbs_to_be_released[i].rb_id);
}
}
......@@ -1166,20 +1143,8 @@ int DU_send_UE_CONTEXT_MODIFICATION_RESPONSE(sctp_assoc_t assoc_id, f1ap_ue_cont
drbs_setupmod_item->dRBID = resp->drbs_to_be_setup[i].drb_id;
for (int j=0; j<resp->drbs_to_be_setup[i].up_dl_tnl_length; j++) {
f1ap_drb_to_be_setup_t *drb = &resp->drbs_to_be_setup[i];
transport_layer_addr_t tl_addr = {0};
memcpy(tl_addr.buffer, &drb->up_ul_tnl[0].tl_address, sizeof(drb->up_ul_tnl[0].tl_address));
tl_addr.length = sizeof(drb->up_ul_tnl[0].tl_address) * 8;
drb->up_dl_tnl[j].teid = newGtpuCreateTunnel(getCxt(0)->gtpInst,
resp->gNB_DU_ue_id,
drb->drb_id,
drb->drb_id,
drb->up_ul_tnl[j].teid,
-1, // no qfi
tl_addr,
drb->up_ul_tnl[0].port,
DURecvCb,
NULL);
const f1ap_up_tnl_t *tnl = &resp->drbs_to_be_setup[i].up_dl_tnl[j];
DevAssert(tnl->teid > 0);
/* ADD */
asn1cSequenceAdd(drbs_setupmod_item->dLUPTNLInformation_ToBeSetup_List.list,
......@@ -1188,12 +1153,9 @@ int DU_send_UE_CONTEXT_MODIFICATION_RESPONSE(sctp_assoc_t assoc_id, f1ap_ue_cont
/* gTPTunnel */
asn1cCalloc(dLUPTNLInformation_ToBeSetup_Item->dLUPTNLInformation.choice.gTPTunnel,gTPTunnel);
/* transportLayerAddress */
struct sockaddr_in addr= {0};
inet_pton(AF_INET, getCxt(0)->net_config.DU_f1_ip_address.ipv4_address, &addr.sin_addr.s_addr);
TRANSPORT_LAYER_ADDRESS_IPv4_TO_BIT_STRING(addr.sin_addr.s_addr,
&gTPTunnel->transportLayerAddress);
TRANSPORT_LAYER_ADDRESS_IPv4_TO_BIT_STRING(tnl->tl_address, &gTPTunnel->transportLayerAddress);
/* gTP_TEID */
INT32_TO_OCTET_STRING(resp->drbs_to_be_setup[i].up_dl_tnl[j].teid, &gTPTunnel->gTP_TEID);
INT32_TO_OCTET_STRING(tnl->teid, &gTPTunnel->gTP_TEID);
} // for j
} // for i
}
......
......@@ -23,8 +23,11 @@
#include "mac_proto.h"
#include "openair2/F1AP/f1ap_ids.h"
#include "openair2/F1AP/f1ap_common.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
#include "F1AP_CauseRadioNetwork.h"
#include "openair3/ocp-gtpu/gtp_itf.h"
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_oai_api.h"
#include "uper_decoder.h"
#include "uper_encoder.h"
......@@ -34,6 +37,59 @@ const uint64_t qos_fiveqi[26] = {1, 2, 3, 4, 65, 66, 67, 71, 72, 73, 74, 76, 5,
const uint64_t qos_priority[26] = {20, 40, 30, 50, 7, 20, 15, 56, 56, 56, 56, 56, 10,
60, 70, 80, 90, 5, 55, 65, 68, 19, 22, 24, 21, 18};
static instance_t get_f1_gtp_instance(void)
{
const f1ap_cudu_inst_t *inst = getCxt(0);
if (!inst)
return -1; // means no F1
return inst->gtpInst;
}
static int drb_gtpu_create(instance_t instance,
uint32_t ue_id,
int incoming_id,
int outgoing_id,
int qfi,
in_addr_t tlAddress, // only IPv4 now
teid_t outgoing_teid,
gtpCallback callBack,
gtpCallbackSDAP callBackSDAP,
gtpv1u_gnb_create_tunnel_resp_t *create_tunnel_resp)
{
gtpv1u_gnb_create_tunnel_req_t create_tunnel_req = {0};
create_tunnel_req.incoming_rb_id[0] = incoming_id;
create_tunnel_req.pdusession_id[0] = outgoing_id;
memcpy(&create_tunnel_req.dst_addr[0].buffer, &tlAddress, sizeof(uint8_t) * 4);
create_tunnel_req.dst_addr[0].length = 32;
create_tunnel_req.outgoing_teid[0] = outgoing_teid;
create_tunnel_req.outgoing_qfi[0] = qfi;
create_tunnel_req.num_tunnels = 1;
create_tunnel_req.ue_id = ue_id;
// we use gtpv1u_create_ngu_tunnel because it returns the interface
// address and port of the interface; apart from that, we also might call
// newGtpuCreateTunnel() directly
return gtpv1u_create_ngu_tunnel(instance, &create_tunnel_req, create_tunnel_resp, callBack, callBackSDAP);
}
bool DURecvCb(protocol_ctxt_t *ctxt_pP,
const srb_flag_t srb_flagP,
const rb_id_t rb_idP,
const mui_t muiP,
const confirm_t confirmP,
const sdu_size_t sdu_buffer_sizeP,
unsigned char *const sdu_buffer_pP,
const pdcp_transmission_mode_t modeP,
const uint32_t *sourceL2Id,
const uint32_t *destinationL2Id)
{
// The buffer comes from the stack in gtp-u thread, we have a make a separate buffer to enqueue in a inter-thread message queue
uint8_t *sdu = malloc16(sdu_buffer_sizeP);
memcpy(sdu, sdu_buffer_pP, sdu_buffer_sizeP);
du_rlc_data_req(ctxt_pP, srb_flagP, false, rb_idP, muiP, confirmP, sdu_buffer_sizeP, sdu);
return true;
}
static long get_lcid_from_drbid(int drb_id)
{
return drb_id + 3; /* LCID is DRB + 3 */
......@@ -133,6 +189,7 @@ static int handle_ue_context_drbs_setup(int rnti,
NR_CellGroupConfig_t *cellGroupConfig)
{
DevAssert(req_drbs != NULL && resp_drbs != NULL && cellGroupConfig != NULL);
instance_t f1inst = get_f1_gtp_instance();
/* Note: the actual GTP tunnels are created in the F1AP breanch of
* ue_context_*_response() */
......@@ -140,12 +197,32 @@ static int handle_ue_context_drbs_setup(int rnti,
AssertFatal(*resp_drbs != NULL, "out of memory\n");
for (int i = 0; i < drbs_len; i++) {
const f1ap_drb_to_be_setup_t *drb = &req_drbs[i];
f1ap_drb_to_be_setup_t *resp_drb = &(*resp_drbs)[i];
NR_RLC_BearerConfig_t *rlc_BearerConfig = get_bearerconfig_from_drb(drb);
nr_rlc_add_drb(rnti, drb->drb_id, rlc_BearerConfig);
(*resp_drbs)[i] = *drb;
*resp_drb = *drb;
// just put same number of tunnels in DL as in UL
(*resp_drbs)[i].up_dl_tnl_length = drb->up_ul_tnl_length;
DevAssert(drb->up_ul_tnl_length == 1);
resp_drb->up_dl_tnl_length = drb->up_ul_tnl_length;
if (f1inst >= 0) { // we actually use F1-U
int qfi = -1; // don't put PDU session marker in GTP
gtpv1u_gnb_create_tunnel_resp_t resp_f1 = {0};
int ret = drb_gtpu_create(f1inst,
rnti,
drb->drb_id,
drb->drb_id,
qfi,
drb->up_ul_tnl[0].tl_address,
drb->up_ul_tnl[0].teid,
DURecvCb,
NULL,
&resp_f1);
AssertFatal(ret >= 0, "Unable to create GTP Tunnel for F1-U\n");
memcpy(&resp_drb->up_dl_tnl[0].tl_address, &resp_f1.gnb_addr.buffer, 4);
resp_drb->up_dl_tnl[0].teid = resp_f1.gnb_NGu_teid[0];
}
int ret = ASN_SEQUENCE_ADD(&cellGroupConfig->rlc_BearerToAddModList->list, rlc_BearerConfig);
DevAssert(ret == 0);
......@@ -159,6 +236,7 @@ static int handle_ue_context_drbs_release(int rnti,
NR_CellGroupConfig_t *cellGroupConfig)
{
DevAssert(req_drbs != NULL && cellGroupConfig != NULL);
instance_t f1inst = get_f1_gtp_instance();
cellGroupConfig->rlc_BearerToReleaseList = calloc(1, sizeof(*cellGroupConfig->rlc_BearerToReleaseList));
AssertFatal(cellGroupConfig->rlc_BearerToReleaseList != NULL, "out of memory\n");
......@@ -178,6 +256,8 @@ static int handle_ue_context_drbs_release(int rnti,
}
if (idx < cellGroupConfig->rlc_BearerToAddModList->list.count) {
nr_rlc_release_entity(rnti, lcid);
if (f1inst >= 0)
newGtpuDeleteOneTunnel(f1inst, rnti, drb->rb_id);
asn_sequence_del(&cellGroupConfig->rlc_BearerToAddModList->list, idx, 1);
long *plcid = malloc(sizeof(*plcid));
AssertFatal(plcid != NULL, "out of memory\n");
......@@ -557,6 +637,10 @@ void ue_context_release_command(const f1ap_ue_context_release_cmd_t *cmd)
return;
}
instance_t f1inst = get_f1_gtp_instance();
if (f1inst >= 0)
newGtpuDeleteAllTunnels(f1inst, cmd->gNB_DU_ue_id);
if (UE->UE_sched_ctrl.ul_failure || cmd->rrc_container_length == 0) {
/* The UE is already not connected anymore or we have nothing to forward*/
nr_mac_release_ue(mac, cmd->gNB_DU_ue_id);
......@@ -632,6 +716,9 @@ void dl_rrc_message_transfer(const f1ap_dl_rrc_message_t *dl_rrc)
pthread_mutex_unlock(&mac->sched_lock);
nr_rlc_remove_ue(dl_rrc->gNB_DU_ue_id);
nr_rlc_update_rnti(*dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id);
instance_t f1inst = get_f1_gtp_instance();
if (f1inst >= 0) // we actually use F1-U
gtpv1u_update_ue_id(f1inst, *dl_rrc->old_gNB_DU_ue_id, dl_rrc->gNB_DU_ue_id);
}
/* the DU ue id is the RNTI */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment