From da915d21e04f8bf95a0776020348f8914d825eb7 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 2 Apr 2025 07:19:43 +0200 Subject: [PATCH] Add F1AP UE context setup request message enc/dec + test Reintroduce separate message types to simplify the encoding/decoding, including shorter names. The old types will be removed once everything has been migrated to the new encoder/decoder, which should ensure that everything is properly migrated to the unit-tested encoder/decoders. Also, for each type of F1 message (F1 UE context setup request, response, modification request, ...) introduce a separate type to avoid to mistakenly use wrong fields, which can lead to bugs. In this regard, the DL PDCP SN bit size in the DRB-ToBeSetup list of the UE context setup request is optional, as it will also be used in the UE context modification request DRB SetupMod list (which avoids quite some code duplication). The encoder checks that this IE is present, though. --- openair2/COMMON/f1ap_messages_types.h | 99 +++ openair2/F1AP/lib/CMakeLists.txt | 3 +- openair2/F1AP/lib/f1ap_lib_includes.h | 5 + openair2/F1AP/lib/f1ap_ue_context.c | 876 ++++++++++++++++++++++++++ openair2/F1AP/lib/f1ap_ue_context.h | 36 ++ openair2/F1AP/tests/CMakeLists.txt | 2 +- openair2/F1AP/tests/f1ap_lib_test.c | 170 +++++ 7 files changed, 1189 insertions(+), 2 deletions(-) create mode 100644 openair2/F1AP/lib/f1ap_ue_context.c create mode 100644 openair2/F1AP/lib/f1ap_ue_context.h diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 84d887f8f9..85f2def720 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -26,6 +26,7 @@ #include #include "common/5g_platform_types.h" #include "common/platform_constants.h" +#include "common/utils/ds/byte_array.h" //-------------------------------------------------------------------------------------------// // Defines to access message fields. @@ -452,6 +453,13 @@ typedef struct cu_to_du_rrc_information_s { uint32_t handoverPreparationInfo_length; }cu_to_du_rrc_information_t; +typedef struct f1ap_cu_to_du_rrc_info_s { + byte_array_t *cg_configinfo; + byte_array_t *ue_cap; + byte_array_t *meas_config; + byte_array_t *ho_prep_info; +} f1ap_cu_to_du_rrc_info_t; + typedef struct du_to_cu_rrc_information_s { uint8_t * cellGroupConfig; uint32_t cellGroupConfig_length; @@ -477,6 +485,97 @@ typedef enum TransmActionInd_e { TransmActionInd_RESTART, } TransmActionInd_t; +typedef struct f1ap_srb_to_setup_t { + int id; +} f1ap_srb_to_setup_t; + +/// 9.3.1.52 Packet Error Rate +typedef struct f1ap_per_t { + uint8_t scalar; + uint8_t exponent; +} f1ap_per_t; + +// 9.3.1.49 Non-Dynamic 5QI Descriptor +typedef struct f1ap_nondynamic_5qi_t { + int fiveQI; +} f1ap_nondynamic_5qi_t; + +// 9.3.1.47 Dynamic 5QI Descriptor +typedef struct f1ap_dynamic_5qi_t { + int prio; /// QoS Priority Level + int pdb; /// Packet Delay Budget + f1ap_per_t per; /// Packet Error Rate + bool *delay_critical; /// Delay Critical + int *avg_win; /// Averaging Window +} f1ap_dynamic_5qi_t; + +typedef struct f1ap_arp_t { + uint16_t prio; + preemption_capability_t preempt_cap; + preemption_vulnerability_t preempt_vuln; +} f1ap_arp_t; + +// 9.3.1.45 QoS Flow Level QoS Parameters +typedef struct f1ap_qos_flow_param_t { + fiveQI_t qos_type; + union { + f1ap_nondynamic_5qi_t nondyn; + f1ap_dynamic_5qi_t dyn; + }; + f1ap_arp_t arp; +} f1ap_qos_flow_param_t; + +// in 9.2.2.1 Flows Mapped to DRB Item +typedef struct f1ap_drb_flows_mapped_t { + int qfi; + f1ap_qos_flow_param_t param; +} f1ap_drb_flows_mapped_t; + +/// in 9.2.2.1 DRB Info in UE Context Setup Request +typedef struct f1ap_drb_info_nr_t { + f1ap_qos_flow_param_t drb_qos; + nssai_t nssai; + int flows_len; + f1ap_drb_flows_mapped_t *flows; +} f1ap_drb_info_nr_t; + +typedef enum { F1AP_PDCP_SN_12B, F1AP_PDCP_SN_18B } f1ap_pdcp_sn_len_t; +/// in 9.2.2.1 DRB to Be Setup Item IEs +typedef struct f1ap_drb_to_setup_t { + int id; + enum { F1AP_QOS_CHOICE_EUTRAN, F1AP_QOS_CHOICE_NR } qos_choice; + union { + // eutran not implemented + f1ap_drb_info_nr_t nr; + }; + int up_ul_tnl_len; + f1ap_up_tnl_t up_ul_tnl[2]; + f1ap_rlc_mode_t rlc_mode; + f1ap_pdcp_sn_len_t *dl_pdcp_sn_len; // in setup mandatory, in setupmod optional + f1ap_pdcp_sn_len_t *ul_pdcp_sn_len; +} f1ap_drb_to_setup_t; + +typedef struct f1ap_ue_context_setup_req_s { + uint32_t gNB_CU_ue_id; + uint32_t *gNB_DU_ue_id; + + plmn_id_t plmn; + uint64_t nr_cellid; + uint8_t servCellIndex; + + f1ap_cu_to_du_rrc_info_t cu_to_du_rrc_info; + + int srbs_len; + f1ap_srb_to_setup_t *srbs; + + int drbs_len; + f1ap_drb_to_setup_t *drbs; + + byte_array_t *rrc_container; + + uint64_t *gnb_du_ue_agg_mbr_ul; // C-ifDRBSetup +} f1ap_ue_context_setup_req_t; + typedef struct f1ap_ue_context_setup_s { uint32_t gNB_CU_ue_id; uint32_t gNB_DU_ue_id; diff --git a/openair2/F1AP/lib/CMakeLists.txt b/openair2/F1AP/lib/CMakeLists.txt index 0c6137dc30..978f69fb29 100644 --- a/openair2/F1AP/lib/CMakeLists.txt +++ b/openair2/F1AP/lib/CMakeLists.txt @@ -2,8 +2,9 @@ add_library(f1ap_lib OBJECT f1ap_lib_common.c f1ap_rrc_message_transfer.c f1ap_interface_management.c + f1ap_ue_context.c ) -target_link_libraries(f1ap_lib PRIVATE asn1_f1ap) +target_link_libraries(f1ap_lib PRIVATE asn1_f1ap ds) target_include_directories(f1ap_lib PUBLIC ${CMAKE_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/openair2/F1AP/lib/f1ap_lib_includes.h b/openair2/F1AP/lib/f1ap_lib_includes.h index 267e7243e7..e34ec0598d 100644 --- a/openair2/F1AP/lib/f1ap_lib_includes.h +++ b/openair2/F1AP/lib/f1ap_lib_includes.h @@ -41,5 +41,10 @@ #include "F1AP_SibtypetobeupdatedListItem.h" #include "F1AP_UnsuccessfulOutcome.h" #include "F1AP_UE-associatedLogicalF1-ConnectionListRes.h" +#include "F1AP_ULUPTNLInformation-ToBeSetup-Item.h" +#include "F1AP_GTPTunnel.h" +#include "F1AP_Flows-Mapped-To-DRB-Item.h" +#include "F1AP_NonDynamic5QIDescriptor.h" +#include "F1AP_Dynamic5QIDescriptor.h" #endif /* F1AP_LIB_INCLUDES_H_ */ diff --git a/openair2/F1AP/lib/f1ap_ue_context.c b/openair2/F1AP/lib/f1ap_ue_context.c new file mode 100644 index 0000000000..572ad3ce6a --- /dev/null +++ b/openair2/F1AP/lib/f1ap_ue_context.c @@ -0,0 +1,876 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#include "f1ap_ue_context.h" + +#include "f1ap_lib_common.h" +#include "f1ap_lib_includes.h" +#include "f1ap_messages_types.h" + +#include "common/utils/assertions.h" +#include "openair3/UTILS/conversions.h" +#include "common/utils/oai_asn1.h" +#include "common/utils/utils.h" +#include "common/utils/ds/byte_array.h" + +static F1AP_CUtoDURRCInformation_t encode_cu_to_du_rrc_info(const f1ap_cu_to_du_rrc_info_t *cu2du) +{ + F1AP_CUtoDURRCInformation_t enc = {0}; + + /* optional: cG_ConfigInfo */ + if (cu2du->cg_configinfo) { + asn1cCalloc(enc.cG_ConfigInfo, cG_ConfigInfo); + const byte_array_t *ba = cu2du->cg_configinfo; + OCTET_STRING_fromBuf(cG_ConfigInfo, (const char *)ba->buf, ba->len); + } + + /* optional: uE_CapabilityRAT_ContainerList */ + if (cu2du->ue_cap) { + asn1cCalloc(enc.uE_CapabilityRAT_ContainerList, uE_CapabilityRAT_ContainerList); + const byte_array_t *ba = cu2du->ue_cap; + OCTET_STRING_fromBuf(uE_CapabilityRAT_ContainerList, (const char *)ba->buf, ba->len); + } + + /* optional: measConfig */ + if (cu2du->meas_config) { + asn1cCalloc(enc.measConfig, measConfig); + const byte_array_t *ba = cu2du->meas_config; + OCTET_STRING_fromBuf(measConfig, (const char *)ba->buf, ba->len); + } + + /* optional: HandoverPreparationInformation */ + if (cu2du->ho_prep_info) { + F1AP_ProtocolExtensionContainer_10696P60_t *p = calloc_or_fail(1, sizeof(*p)); + enc.iE_Extensions = (struct F1AP_ProtocolExtensionContainer *)p; + asn1cSequenceAdd(p->list, F1AP_CUtoDURRCInformation_ExtIEs_t, ie_ext); + ie_ext->id = F1AP_ProtocolIE_ID_id_HandoverPreparationInformation; + ie_ext->criticality = F1AP_Criticality_ignore; + ie_ext->extensionValue.present = F1AP_CUtoDURRCInformation_ExtIEs__extensionValue_PR_HandoverPreparationInformation; + const byte_array_t *ba = cu2du->ho_prep_info; + F1AP_HandoverPreparationInformation_t *hpi = &ie_ext->extensionValue.choice.HandoverPreparationInformation; + OCTET_STRING_fromBuf(hpi, (const char *)ba->buf, ba->len); + } + return enc; +} + +static bool decode_cu_to_du_rrc_info(f1ap_cu_to_du_rrc_info_t *dec, const F1AP_CUtoDURRCInformation_t *cu2du) +{ + if (cu2du->cG_ConfigInfo) { + dec->cg_configinfo = calloc_or_fail(1, sizeof(*dec->cg_configinfo)); + const F1AP_CG_ConfigInfo_t *cgci = cu2du->cG_ConfigInfo; + *dec->cg_configinfo = create_byte_array(cgci->size, (uint8_t*)cgci->buf); + } + if (cu2du->uE_CapabilityRAT_ContainerList) { + dec->ue_cap = calloc_or_fail(1, sizeof(*dec->ue_cap)); + const F1AP_UE_CapabilityRAT_ContainerList_t *l = cu2du->uE_CapabilityRAT_ContainerList; + *dec->ue_cap = create_byte_array(l->size, (uint8_t*)l->buf); + } + if (cu2du->measConfig) { + dec->meas_config = calloc_or_fail(1, sizeof(*dec->meas_config)); + const F1AP_MeasConfig_t *mc = cu2du->measConfig; + *dec->meas_config = create_byte_array(mc->size, (uint8_t*)mc->buf); + } + if (cu2du->iE_Extensions) { + const F1AP_ProtocolExtensionContainer_10696P60_t *ext = (const F1AP_ProtocolExtensionContainer_10696P60_t *)cu2du->iE_Extensions; + for (int i = 0; i < ext->list.count; ++i) { + const F1AP_CUtoDURRCInformation_ExtIEs_t *cu2du_info_ext = ext->list.array[i]; + switch (cu2du_info_ext->id) { + case F1AP_ProtocolIE_ID_id_HandoverPreparationInformation: + dec->ho_prep_info = calloc_or_fail(1, sizeof(*dec->ho_prep_info)); + const F1AP_HandoverPreparationInformation_t *hopi = &cu2du_info_ext->extensionValue.choice.HandoverPreparationInformation; + *dec->ho_prep_info = create_byte_array(hopi->size, (uint8_t*) hopi->buf); + break; + default: + PRINT_ERROR("received unsupported F1AP CUtoDURRCInfo extension container %ld\n", cu2du_info_ext->id); + } + } + } + return true; +} + +static f1ap_cu_to_du_rrc_info_t cp_cu_to_du_rrc_info(const f1ap_cu_to_du_rrc_info_t *src) +{ + f1ap_cu_to_du_rrc_info_t dst = {0}; + CP_OPT_BYTE_ARRAY(dst.cg_configinfo, src->cg_configinfo); + CP_OPT_BYTE_ARRAY(dst.ue_cap, src->ue_cap); + CP_OPT_BYTE_ARRAY(dst.meas_config, src->meas_config); + CP_OPT_BYTE_ARRAY(dst.ho_prep_info, src->ho_prep_info); + return dst; +} + +static bool eq_ba(const byte_array_t a, const byte_array_t b) +{ + return eq_byte_array(&a, &b); +} + +static bool eq_cu_to_du_rrc_info(const f1ap_cu_to_du_rrc_info_t *a, const f1ap_cu_to_du_rrc_info_t *b) +{ + _F1_EQ_CHECK_OPTIONAL_IE(a, b, cg_configinfo, eq_ba); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, ue_cap, eq_ba); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, meas_config, eq_ba); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, ho_prep_info, eq_ba); + return true; +} + +static void free_cu_to_du_rrc_info(f1ap_cu_to_du_rrc_info_t *cu2du) +{ + FREE_OPT_BYTE_ARRAY(cu2du->cg_configinfo); + FREE_OPT_BYTE_ARRAY(cu2du->ue_cap); + FREE_OPT_BYTE_ARRAY(cu2du->meas_config); + FREE_OPT_BYTE_ARRAY(cu2du->ho_prep_info); +} + +/* \brief Encode SRB-ToBeSetup List, for UE Context Setup Request, from + * f1ap_srb_to_setup_t. */ +static F1AP_SRBs_ToBeSetup_List_t encode_srbs_to_setup(int n, const f1ap_srb_to_setup_t *srbs) +{ + F1AP_SRBs_ToBeSetup_List_t list = {0}; + for (int i = 0; i < n; ++i) { + asn1cSequenceAdd(list, F1AP_SRBs_ToBeSetup_ItemIEs_t, itie); + itie->id = F1AP_ProtocolIE_ID_id_SRBs_ToBeSetup_Item; + itie->criticality = F1AP_Criticality_reject; + itie->value.present = F1AP_SRBs_ToBeSetup_ItemIEs__value_PR_SRBs_ToBeSetup_Item; + itie->value.choice.SRBs_ToBeSetup_Item.sRBID = srbs[i].id; + } + return list; +} + +/* \brief Decode SRB-ToBeSetup List, from UE Context Setup Request, into + * f1ap_srb_to_setup_t. */ +static bool decode_srbs_to_setup(const F1AP_SRBs_ToBeSetup_List_t *f1ap, int *n, f1ap_srb_to_setup_t **out) +{ + DevAssert(out != NULL); + *out = NULL; + int count = *n = f1ap->list.count; + if (count == 0) + return true; + + f1ap_srb_to_setup_t *srbs = *out = calloc_or_fail(count, sizeof(*srbs)); + for (int i = 0; i < count; ++i) { + const F1AP_SRBs_ToBeSetup_ItemIEs_t *itie = (F1AP_SRBs_ToBeSetup_ItemIEs_t *)f1ap->list.array[i]; + _F1_EQ_CHECK_LONG(itie->id, F1AP_ProtocolIE_ID_id_SRBs_ToBeSetup_Item); + _F1_EQ_CHECK_INT(itie->value.present, F1AP_SRBs_ToBeSetup_ItemIEs__value_PR_SRBs_ToBeSetup_Item); + srbs[i].id = itie->value.choice.SRBs_ToBeSetup_Item.sRBID; + } + + return true; +} + +static f1ap_srb_to_setup_t cp_srb_to_setup(const f1ap_srb_to_setup_t *orig) +{ + f1ap_srb_to_setup_t cp = { + .id = orig->id, + }; + return cp; +} + +static bool eq_srb_to_setup(const f1ap_srb_to_setup_t *a, const f1ap_srb_to_setup_t *b) +{ + _F1_EQ_CHECK_INT(a->id, b->id); + return true; +} + +static void free_srb_to_setup(f1ap_srb_to_setup_t *srbs) +{ + // nothing to free +} + +static F1AP_QoSFlowLevelQoSParameters_t encode_qos_flow_param(const f1ap_qos_flow_param_t *p) +{ + F1AP_QoSFlowLevelQoSParameters_t f1ap = {0}; + + 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); + nd->fiveQI = p->nondyn.fiveQI; + } else { + DevAssert(p->qos_type == DYNAMIC); + f1ap.qoS_Characteristics.present = F1AP_QoS_Characteristics_PR_dynamic_5QI; + asn1cCalloc(f1ap.qoS_Characteristics.choice.dynamic_5QI, d); + + d->qoSPriorityLevel = p->dyn.prio; + d->packetDelayBudget = p->dyn.pdb; + d->packetErrorRate.pER_Scalar = p->dyn.per.scalar; + d->packetErrorRate.pER_Exponent = p->dyn.per.exponent; + + if (p->dyn.delay_critical) { + e_F1AP_Dynamic5QIDescriptor__delayCritical v = *p->dyn.delay_critical + ? F1AP_Dynamic5QIDescriptor__delayCritical_delay_critical + : F1AP_Dynamic5QIDescriptor__delayCritical_non_delay_critical; + asn1cCallocOne(d->delayCritical, v); + } + + if (p->dyn.avg_win) + asn1cCallocOne(d->averagingWindow, *p->dyn.avg_win); + } + + F1AP_NGRANAllocationAndRetentionPriority_t *arp = &f1ap.nGRANallocationRetentionPriority; + arp->priorityLevel = p->arp.prio; + arp->pre_emptionCapability = p->arp.preempt_cap; + arp->pre_emptionVulnerability = p->arp.preempt_vuln; + return f1ap; +} + +static bool decode_qos_flow_param(const F1AP_QoSFlowLevelQoSParameters_t *f1ap, f1ap_qos_flow_param_t *out) +{ + if (f1ap->qoS_Characteristics.present == F1AP_QoS_Characteristics_PR_non_Dynamic_5QI) { + out->qos_type = NON_DYNAMIC; + const F1AP_NonDynamic5QIDescriptor_t *nondyn = f1ap->qoS_Characteristics.choice.non_Dynamic_5QI; + out->nondyn.fiveQI = nondyn->fiveQI; + } else { + _F1_EQ_CHECK_INT(f1ap->qoS_Characteristics.present, F1AP_QoS_Characteristics_PR_dynamic_5QI); + const F1AP_Dynamic5QIDescriptor_t *d = f1ap->qoS_Characteristics.choice.dynamic_5QI; + out->qos_type = DYNAMIC; + out->dyn.prio = d->qoSPriorityLevel; + out->dyn.pdb = d->packetDelayBudget; + out->dyn.per.scalar = d->packetErrorRate.pER_Scalar; + out->dyn.per.exponent = d->packetErrorRate.pER_Exponent; + + if (d->delayCritical) { + bool v = *d->delayCritical == F1AP_Dynamic5QIDescriptor__delayCritical_delay_critical; + _F1_MALLOC(out->dyn.delay_critical, v); + } + + if (d->averagingWindow) + _F1_MALLOC(out->dyn.avg_win, *d->averagingWindow); + } + + const F1AP_NGRANAllocationAndRetentionPriority_t *arp = &f1ap->nGRANallocationRetentionPriority; + out->arp.prio = arp->priorityLevel; + out->arp.preempt_cap = arp->pre_emptionCapability; + out->arp.preempt_vuln = arp->pre_emptionVulnerability; + return true; +} + +static f1ap_qos_flow_param_t cp_qos_flow_param(const f1ap_qos_flow_param_t *orig) +{ + f1ap_qos_flow_param_t cp = { + .qos_type = orig->qos_type, + // dyn/nondyn below + .arp = orig->arp, + }; + if (cp.qos_type == NON_DYNAMIC) { + cp.nondyn.fiveQI = orig->nondyn.fiveQI; + } else { + DevAssert(cp.qos_type == DYNAMIC); + f1ap_dynamic_5qi_t *cpdyn = &cp.dyn; + const f1ap_dynamic_5qi_t *odyn = &orig->dyn; + cpdyn->prio = odyn->prio; + cpdyn->pdb = odyn->pdb; + cpdyn->per.scalar = odyn->per.scalar; + cpdyn->per.exponent = odyn->per.exponent; + if (odyn->delay_critical) + _F1_MALLOC(cpdyn->delay_critical, *odyn->delay_critical); + if (odyn->avg_win) + _F1_MALLOC(cpdyn->avg_win, *odyn->avg_win); + } + return cp; +} + +static bool eq_qos_flow_param(const f1ap_qos_flow_param_t *a, const f1ap_qos_flow_param_t *b) +{ + _F1_EQ_CHECK_INT(a->qos_type, b->qos_type); + if (a->qos_type == NON_DYNAMIC) { + _F1_EQ_CHECK_INT(a->nondyn.fiveQI, b->nondyn.fiveQI); + } else { + DevAssert(a->qos_type == DYNAMIC); + DevAssert(b->qos_type == DYNAMIC); + const f1ap_dynamic_5qi_t *adyn = &a->dyn, *bdyn = &b->dyn; + _F1_EQ_CHECK_INT(adyn->prio, bdyn->prio); + _F1_EQ_CHECK_INT(adyn->pdb, bdyn->pdb); + _F1_EQ_CHECK_INT(adyn->per.scalar, bdyn->per.scalar); + _F1_EQ_CHECK_INT(adyn->per.exponent, bdyn->per.exponent); + _F1_EQ_CHECK_OPTIONAL_IE(adyn, bdyn, delay_critical, _F1_EQ_CHECK_INT); + _F1_EQ_CHECK_OPTIONAL_IE(adyn, bdyn, avg_win, _F1_EQ_CHECK_INT); + } + _F1_EQ_CHECK_INT(a->arp.prio, b->arp.prio); + _F1_EQ_CHECK_INT(a->arp.preempt_cap, b->arp.preempt_cap); + _F1_EQ_CHECK_INT(a->arp.preempt_vuln, b->arp.preempt_vuln); + return true; +} + +static void free_qos_flow_param(f1ap_qos_flow_param_t *p) +{ + if (p->qos_type == NON_DYNAMIC) { + // nothing to free + } else { + DevAssert(p->qos_type == DYNAMIC); + free(p->dyn.delay_critical); + free(p->dyn.avg_win); + } + // arp: nothing to free +} + +static F1AP_DRB_Information_t encode_drb_info_nr(const f1ap_drb_info_nr_t *drb) +{ + F1AP_DRB_Information_t f1ap = {0}; + + f1ap.dRB_QoS = encode_qos_flow_param(&drb->drb_qos); + f1ap.sNSSAI = encode_nssai(&drb->nssai); + + for (int i = 0; i < drb->flows_len; ++i) { + asn1cSequenceAdd(f1ap.flows_Mapped_To_DRB_List.list, F1AP_Flows_Mapped_To_DRB_Item_t, it); + it->qoSFlowIdentifier = drb->flows[i].qfi; + it->qoSFlowLevelQoSParameters = encode_qos_flow_param(&drb->flows[i].param); + } + + return f1ap; +} + +static bool decode_drb_info_nr(const F1AP_DRB_Information_t *f1ap, f1ap_drb_info_nr_t *out) +{ + _F1_CHECK_EXP(decode_qos_flow_param(&f1ap->dRB_QoS, &out->drb_qos)); + out->nssai = decode_nssai(&f1ap->sNSSAI); + out->flows_len = f1ap->flows_Mapped_To_DRB_List.list.count; + out->flows = calloc_or_fail(out->flows_len, sizeof(*out->flows)); + for (int i = 0; i < out->flows_len; ++i) { + const F1AP_Flows_Mapped_To_DRB_Item_t *it = f1ap->flows_Mapped_To_DRB_List.list.array[i]; + out->flows[i].qfi = it->qoSFlowIdentifier; + _F1_CHECK_EXP(decode_qos_flow_param(&it->qoSFlowLevelQoSParameters, &out->flows[i].param)); + } + return true; +} + +static f1ap_drb_info_nr_t cp_drb_info_nr(const f1ap_drb_info_nr_t *orig) +{ + f1ap_drb_info_nr_t cp = { + .drb_qos = cp_qos_flow_param(&orig->drb_qos), + .nssai = orig->nssai, + .flows_len = orig->flows_len, + // flows below + }; + cp.flows = calloc_or_fail(cp.flows_len, sizeof(*cp.flows)); + for (int i = 0; i < cp.flows_len; ++i) { + cp.flows[i].qfi = orig->flows[i].qfi; + cp.flows[i].param = cp_qos_flow_param(&orig->flows[i].param); + } + return cp; +} + +static bool eq_drb_info_nr(const f1ap_drb_info_nr_t *a, const f1ap_drb_info_nr_t *b) +{ + _F1_CHECK_EXP(eq_qos_flow_param(&a->drb_qos, &b->drb_qos)); + _F1_EQ_CHECK_INT(a->nssai.sst, b->nssai.sst); + _F1_EQ_CHECK_INT(a->nssai.sd, b->nssai.sd); + _F1_EQ_CHECK_INT(a->flows_len, b->flows_len); + for (int i = 0; i < a->flows_len; ++i) { + _F1_EQ_CHECK_INT(a->flows[i].qfi, b->flows[i].qfi); + _F1_CHECK_EXP(eq_qos_flow_param(&a->flows[i].param, &b->flows[i].param)); + } + return true; +} + +static void free_drb_info_nr(f1ap_drb_info_nr_t *p) +{ + free_qos_flow_param(&p->drb_qos); + // nssai: nothing + for (int i = 0; i < p->flows_len; ++i) + free_qos_flow_param(&p->flows[i].param); + free(p->flows); +} + +static F1AP_RLCMode_t rlc_mode_to_asn1(f1ap_rlc_mode_t mode) +{ + switch (mode) { + case F1AP_RLC_MODE_AM: + return F1AP_RLCMode_rlc_am; + case F1AP_RLC_MODE_UM_BIDIR: + return F1AP_RLCMode_rlc_um_bidirectional; + default: + break; + } + AssertFatal(false, "unsupported RLC Mode %d received\n", mode); + return F1AP_RLCMode_rlc_am; // won't be reached +} + +static bool rlc_mode_from_asn1(F1AP_RLCMode_t mode, f1ap_rlc_mode_t *out) +{ + switch (mode) { + case F1AP_RLCMode_rlc_am: + *out = F1AP_RLC_MODE_AM; + return true; + case F1AP_RLCMode_rlc_um_bidirectional: + *out = F1AP_RLC_MODE_UM_BIDIR; + return true; + default: + PRINT_ERROR("unsupported RLC Mode %ld received: setting AM\n", mode); + break; + } + return false; +} + +static F1AP_UPTransportLayerInformation_t encode_up_tnl(const f1ap_up_tnl_t *tnl) +{ + F1AP_UPTransportLayerInformation_t tnl_info = { .present = F1AP_UPTransportLayerInformation_PR_gTPTunnel, }; + asn1cCalloc(tnl_info.choice.gTPTunnel, gtp_tnl); + TRANSPORT_LAYER_ADDRESS_IPv4_TO_BIT_STRING(tnl->tl_address, >p_tnl->transportLayerAddress); + INT32_TO_OCTET_STRING(tnl->teid, >p_tnl->gTP_TEID); + return tnl_info; +} + +static bool decode_up_tnl(const F1AP_UPTransportLayerInformation_t *tnl_info, f1ap_up_tnl_t *out) +{ + _F1_EQ_CHECK_INT(tnl_info->present, F1AP_UPTransportLayerInformation_PR_gTPTunnel); + const F1AP_GTPTunnel_t *gtp_tnl = tnl_info->choice.gTPTunnel; + BIT_STRING_TO_TRANSPORT_LAYER_ADDRESS_IPv4(>p_tnl->transportLayerAddress, out->tl_address); + OCTET_STRING_TO_UINT32(>p_tnl->gTP_TEID, out->teid); + return true; +} + +static f1ap_up_tnl_t cp_up_tnl(const f1ap_up_tnl_t *orig) +{ + f1ap_up_tnl_t cp = { .teid = orig->teid, }; + memcpy(&cp.tl_address, &orig->tl_address, sizeof(orig->tl_address)); + return cp; +} + +static bool eq_up_tnl(const f1ap_up_tnl_t *a, const f1ap_up_tnl_t *b) +{ + if (memcmp(&a->tl_address, &b->tl_address, sizeof(a->tl_address)) != 0) { + PRINT_ERROR("tl_address mismatch\n"); + return false; + } + _F1_EQ_CHECK_INT(a->teid, b->teid); + return true; +} + +static void free_up_tnl(const f1ap_up_tnl_t *tnl) +{ + // nothing to free +} + +/* Encode a DRBs_ToSetup list for UE Context Setup Request, from f1ap_drb_to_setup_t. */ +static F1AP_DRBs_ToBeSetup_List_t encode_drbs_to_setup(int n, const f1ap_drb_to_setup_t *drbs) +{ + F1AP_DRBs_ToBeSetup_List_t list = {0}; + for (int i = 0; i < n; ++i) { + const f1ap_drb_to_setup_t *drb = &drbs[i]; + asn1cSequenceAdd(list, F1AP_DRBs_ToBeSetup_ItemIEs_t, itie); + itie->id = F1AP_ProtocolIE_ID_id_DRBs_ToBeSetup_Item; + itie->criticality = F1AP_Criticality_reject; + itie->value.present = F1AP_DRBs_ToBeSetup_ItemIEs__value_PR_DRBs_ToBeSetup_Item; + + F1AP_DRBs_ToBeSetup_Item_t *it = &itie->value.choice.DRBs_ToBeSetup_Item; + it->dRBID = drb->id; + AssertFatal(drb->qos_choice == F1AP_QOS_CHOICE_NR, "only NR QoS choice implemented\n"); + it->qoSInformation.present = F1AP_QoSInformation_PR_choice_extension; + F1AP_QoSInformation_ExtIEs_t *qos_ext_ie = calloc_or_fail(1, sizeof(*qos_ext_ie)); + it->qoSInformation.choice.choice_extension = (struct F1AP_ProtocolIE_SingleContainer *)qos_ext_ie; + qos_ext_ie->id = F1AP_ProtocolIE_ID_id_DRB_Information; + qos_ext_ie->criticality = F1AP_Criticality_reject; + qos_ext_ie->value.present = F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information; + qos_ext_ie->value.choice.DRB_Information = encode_drb_info_nr(&drb->nr); + + /* 12.1.3 uLUPTNLInformation_ToBeSetup_List */ + for (int j = 0; j < drb->up_ul_tnl_len; j++) { + DevAssert(drb->up_ul_tnl[j].teid > 0); + asn1cSequenceAdd(it->uLUPTNLInformation_ToBeSetup_List.list, F1AP_ULUPTNLInformation_ToBeSetup_Item_t, tnl_it); + tnl_it->uLUPTNLInformation = encode_up_tnl(&drb->up_ul_tnl[j]); + } + + it->rLCMode = rlc_mode_to_asn1(drb->rlc_mode); + + F1AP_ProtocolExtensionContainer_10696P82_t *ext = calloc_or_fail(1, sizeof(*ext)); + it->iE_Extensions = (struct F1AP_ProtocolExtensionContainer *)ext; + asn1cSequenceAdd(ext->list, F1AP_DRBs_ToBeSetup_ItemExtIEs_t, ext_ie); + ext_ie->id = F1AP_ProtocolIE_ID_id_DLPDCPSNLength; + ext_ie->criticality = F1AP_Criticality_ignore; + ext_ie->extensionValue.present = F1AP_DRBs_ToBeSetup_ItemExtIEs__extensionValue_PR_PDCPSNLength; + AssertFatal(drb->dl_pdcp_sn_len != NULL, "dl_pdcp_sn_len required for setup\n"); + ext_ie->extensionValue.choice.PDCPSNLength = *drb->dl_pdcp_sn_len; + + if (drb->ul_pdcp_sn_len) { + asn1cSequenceAdd(ext->list, F1AP_DRBs_ToBeSetup_ItemExtIEs_t, ext_ie); + ext_ie->id = F1AP_ProtocolIE_ID_id_ULPDCPSNLength; + ext_ie->criticality = F1AP_Criticality_ignore; + ext_ie->extensionValue.present = F1AP_DRBs_ToBeSetup_ItemExtIEs__extensionValue_PR_PDCPSNLength_1; + ext_ie->extensionValue.choice.PDCPSNLength_1 = *drb->ul_pdcp_sn_len; + } + } + return list; +} + +/* \brief Decode DRBS-ToBeSetup list from UE Context Setup request, into + * f1ap_drb_to_setup_t. */ +static bool decode_drbs_to_setup(const F1AP_DRBs_ToBeSetup_List_t *f1ap, int *n, f1ap_drb_to_setup_t **out) +{ + DevAssert(out != NULL); + *out = NULL; + int count = *n = f1ap->list.count; + if (count == 0) + return true; + + f1ap_drb_to_setup_t *drbs = *out = calloc_or_fail(count, sizeof(*drbs)); + for (int i = 0; i < count; ++i) { + const F1AP_DRBs_ToBeSetup_ItemIEs_t *itie = (const F1AP_DRBs_ToBeSetup_ItemIEs_t *)f1ap->list.array[i]; + _F1_EQ_CHECK_LONG(itie->id, F1AP_ProtocolIE_ID_id_DRBs_ToBeSetup_Item); + _F1_EQ_CHECK_INT(itie->value.present, F1AP_DRBs_ToBeSetup_ItemIEs__value_PR_DRBs_ToBeSetup_Item); + const F1AP_DRBs_ToBeSetup_Item_t *it = &itie->value.choice.DRBs_ToBeSetup_Item; + + f1ap_drb_to_setup_t *drb = &drbs[i]; + drb->id = it->dRBID; + + _F1_EQ_CHECK_INT(it->qoSInformation.present, F1AP_QoSInformation_PR_choice_extension); + const F1AP_QoSInformation_ExtIEs_t *qos_ext = (F1AP_QoSInformation_ExtIEs_t *)it->qoSInformation.choice.choice_extension; + _F1_EQ_CHECK_LONG(qos_ext->id, F1AP_ProtocolIE_ID_id_DRB_Information); + _F1_EQ_CHECK_INT(qos_ext->value.present, F1AP_QoSInformation_ExtIEs__value_PR_DRB_Information); + drb->qos_choice = F1AP_QOS_CHOICE_NR; + _F1_CHECK_EXP(decode_drb_info_nr(&qos_ext->value.choice.DRB_Information, &drb->nr)); + + drb->up_ul_tnl_len = it->uLUPTNLInformation_ToBeSetup_List.list.count; + _F1_EQ_CHECK_GENERIC(drb->up_ul_tnl_len > 0 && drb->up_ul_tnl_len <= 2, "%d", drb->up_ul_tnl_len, 1); + for (int j = 0; j < drb->up_ul_tnl_len; ++j) { + const F1AP_ULUPTNLInformation_ToBeSetup_Item_t *it_tnl = it->uLUPTNLInformation_ToBeSetup_List.list.array[j]; + _F1_CHECK_EXP(decode_up_tnl(&it_tnl->uLUPTNLInformation, &drb->up_ul_tnl[j])); + } + + _F1_CHECK_EXP(rlc_mode_from_asn1(it->rLCMode, &drb->rlc_mode)); + + _F1_CHECK_EXP(it->iE_Extensions); // PDCP SN length is under extension, is mandatory + const F1AP_ProtocolExtensionContainer_10696P82_t *ext = (const F1AP_ProtocolExtensionContainer_10696P82_t *)it->iE_Extensions; + const F1AP_DRBs_ToBeSetup_ItemExtIEs_t *ie; + F1AP_LIB_FIND_IE(F1AP_DRBs_ToBeSetup_ItemExtIEs_t, ie, &ext->list, F1AP_ProtocolIE_ID_id_DLPDCPSNLength, true); + + for (int j = 0; j < ext->list.count; ++j) { + ie = ext->list.array[j]; + AssertError(ie != NULL, return false, "ext->list.array[j] is NULL"); + switch (ie->id) { + case F1AP_ProtocolIE_ID_id_DLPDCPSNLength: + _F1_EQ_CHECK_INT(ie->extensionValue.present, F1AP_DRBs_ToBeSetup_ItemExtIEs__extensionValue_PR_PDCPSNLength); + _F1_MALLOC(drb->dl_pdcp_sn_len, ie->extensionValue.choice.PDCPSNLength); + break; + case F1AP_ProtocolIE_ID_id_ULPDCPSNLength: + _F1_EQ_CHECK_INT(ie->extensionValue.present, F1AP_DRBs_ToBeSetup_ItemExtIEs__extensionValue_PR_PDCPSNLength_1); + _F1_MALLOC(drb->ul_pdcp_sn_len, ie->extensionValue.choice.PDCPSNLength_1); + break; + default: + PRINT_ERROR("F1AP_ProtocolIE_ID_id %ld unknown, skipping\n", ie->id); + break; + } + } + } + + return true; +} + +static f1ap_drb_to_setup_t cp_drb_to_setup(const f1ap_drb_to_setup_t *orig) +{ + f1ap_drb_to_setup_t cp = { + .id = orig->id, + .qos_choice = orig->qos_choice, + // nr below + .up_ul_tnl_len = orig->up_ul_tnl_len, + .up_ul_tnl[0] = cp_up_tnl(&orig->up_ul_tnl[0]), + .up_ul_tnl[1] = cp_up_tnl(&orig->up_ul_tnl[1]), + .rlc_mode = orig->rlc_mode, + // dl_pdcp_sn_len below + // ul_pdcp_sn_len below + }; + AssertFatal(cp.qos_choice == F1AP_QOS_CHOICE_NR, "only NR QoS choice implemented\n"); + if (orig->qos_choice == F1AP_QOS_CHOICE_NR) + cp.nr = cp_drb_info_nr(&orig->nr); + if (orig->dl_pdcp_sn_len) { + _F1_MALLOC(cp.dl_pdcp_sn_len, *orig->dl_pdcp_sn_len); + } + if (orig->ul_pdcp_sn_len) { + _F1_MALLOC(cp.ul_pdcp_sn_len, *orig->ul_pdcp_sn_len); + } + return cp; +} + +static bool eq_drb_to_setup(const f1ap_drb_to_setup_t *a, const f1ap_drb_to_setup_t *b) +{ + _F1_EQ_CHECK_INT(a->id, b->id); + _F1_EQ_CHECK_INT(a->qos_choice, b->qos_choice); + AssertFatal(a->qos_choice == F1AP_QOS_CHOICE_NR, "only NR QoS choice implemented\n"); + if (a->qos_choice == F1AP_QOS_CHOICE_NR) + eq_drb_info_nr(&a->nr, &b->nr); + _F1_EQ_CHECK_INT(a->up_ul_tnl_len, b->up_ul_tnl_len); + for (int i = 0; i < a->up_ul_tnl_len; ++i) { + _F1_CHECK_EXP(eq_up_tnl(&a->up_ul_tnl[i], &b->up_ul_tnl[i])); + } + _F1_EQ_CHECK_INT(a->rlc_mode, b->rlc_mode); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, dl_pdcp_sn_len, _F1_EQ_CHECK_INT); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, ul_pdcp_sn_len, _F1_EQ_CHECK_INT); + return true; +} + +static void free_drb_to_setup(f1ap_drb_to_setup_t *drb) +{ + if (drb->qos_choice == F1AP_QOS_CHOICE_NR) + free_drb_info_nr(&drb->nr); + for (int i = 0; i < drb->up_ul_tnl_len; ++i) + free_up_tnl(&drb->up_ul_tnl[i]); + free(drb->dl_pdcp_sn_len); + free(drb->ul_pdcp_sn_len); +} + +/** + * @brief Encode F1 UE context setup request to ASN.1 + */ +F1AP_F1AP_PDU_t *encode_ue_context_setup_req(const f1ap_ue_context_setup_req_t *req) +{ + F1AP_F1AP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); + + /* Message Type */ + pdu->present = F1AP_F1AP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, tmp); + tmp->procedureCode = F1AP_ProcedureCode_id_UEContextSetup; + tmp->criticality = F1AP_Criticality_reject; + tmp->value.present = F1AP_InitiatingMessage__value_PR_UEContextSetupRequest; + F1AP_UEContextSetupRequest_t *out = &tmp->value.choice.UEContextSetupRequest; + + /* mandatory: GNB_CU_UE_F1AP_ID */ + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie1); + ie1->id = F1AP_ProtocolIE_ID_id_gNB_CU_UE_F1AP_ID; + ie1->criticality = F1AP_Criticality_reject; + ie1->value.present = F1AP_UEContextSetupRequestIEs__value_PR_GNB_CU_UE_F1AP_ID; + ie1->value.choice.GNB_CU_UE_F1AP_ID = req->gNB_CU_ue_id; + + /* optional: GNB_DU_UE_F1AP_ID */ + if (req->gNB_DU_ue_id) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie); + ie->id = F1AP_ProtocolIE_ID_id_gNB_DU_UE_F1AP_ID; + ie->criticality = F1AP_Criticality_ignore; + ie->value.present = F1AP_UEContextSetupRequestIEs__value_PR_GNB_DU_UE_F1AP_ID; + ie->value.choice.GNB_DU_UE_F1AP_ID = *req->gNB_DU_ue_id; + } + + /* mandatory: SpCell ID/NRCGI */ + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie3); + ie3->id = F1AP_ProtocolIE_ID_id_SpCell_ID; + ie3->criticality = F1AP_Criticality_reject; + ie3->value.present = F1AP_UEContextSetupRequestIEs__value_PR_NRCGI; + F1AP_NRCGI_t *nrcgi = &ie3->value.choice.NRCGI; + const plmn_id_t *plmn = &req->plmn; + MCC_MNC_TO_PLMNID(plmn->mcc,plmn->mnc,plmn->mnc_digit_length, &nrcgi->pLMN_Identity); + NR_CELL_ID_TO_BIT_STRING(req->nr_cellid, &nrcgi->nRCellIdentity); + + /* mandatory: ServCellIndox */ + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie4); + ie4->id = F1AP_ProtocolIE_ID_id_ServCellIndex; + ie4->criticality = F1AP_Criticality_reject; + ie4->value.present = F1AP_UEContextSetupRequestIEs__value_PR_ServCellIndex; + ie4->value.choice.ServCellIndex = req->servCellIndex; + + /* mandatory: CUtoDURRCInformation */ + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie6); + ie6->id = F1AP_ProtocolIE_ID_id_CUtoDURRCInformation; + ie6->criticality = F1AP_Criticality_reject; + ie6->value.present = F1AP_UEContextSetupRequestIEs__value_PR_CUtoDURRCInformation; + ie6->value.choice.CUtoDURRCInformation = encode_cu_to_du_rrc_info(&req->cu_to_du_rrc_info); + + /* optional: SRB to be setup list */ + if (req->srbs_len > 0) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie); + ie->id = F1AP_ProtocolIE_ID_id_SRBs_ToBeSetup_List; + ie->criticality = F1AP_Criticality_reject; + ie->value.present = F1AP_UEContextSetupRequestIEs__value_PR_SRBs_ToBeSetup_List; + ie->value.choice.SRBs_ToBeSetup_List = encode_srbs_to_setup(req->srbs_len, req->srbs); + } + + /* optional: DRB to be setup list */ + if (req->drbs_len > 0) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie); + ie->id = F1AP_ProtocolIE_ID_id_DRBs_ToBeSetup_List; + ie->criticality = F1AP_Criticality_reject; + ie->value.present = F1AP_UEContextSetupRequestIEs__value_PR_DRBs_ToBeSetup_List; + ie->value.choice.DRBs_ToBeSetup_List = encode_drbs_to_setup(req->drbs_len, req->drbs); + } + + /* optional: RRC container */ + if (req->rrc_container) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie); + ie->id = F1AP_ProtocolIE_ID_id_RRCContainer; + ie->criticality = F1AP_Criticality_ignore; + ie->value.present = F1AP_UEContextSetupRequestIEs__value_PR_RRCContainer; + OCTET_STRING_fromBuf(&ie->value.choice.RRCContainer, (const char *)req->rrc_container->buf, req->rrc_container->len); + } + + /* conditional if DRB setup list: gNB-DU UE Aggregate Maximum Bit Rate Uplink */ + if (req->gnb_du_ue_agg_mbr_ul) { + asn1cSequenceAdd(out->protocolIEs.list, F1AP_UEContextSetupRequestIEs_t, ie); + ie->id = F1AP_ProtocolIE_ID_id_GNB_DU_UE_AMBR_UL; + ie->criticality = F1AP_Criticality_ignore; + ie->value.present = F1AP_UEContextSetupRequestIEs__value_PR_BitRate; + asn_long2INTEGER(&ie->value.choice.BitRate, *req->gnb_du_ue_agg_mbr_ul); + } + + return pdu; +} + +/** + * @brief Decode F1 UE Context Setup Request + */ +bool decode_ue_context_setup_req(const F1AP_F1AP_PDU_t *pdu, f1ap_ue_context_setup_req_t *out) +{ + DevAssert(out != NULL); + memset(out, 0, sizeof(*out)); + + F1AP_UEContextSetupRequest_t *in = &pdu->choice.initiatingMessage->value.choice.UEContextSetupRequest; + F1AP_UEContextSetupRequestIEs_t *ie; + + F1AP_LIB_FIND_IE(F1AP_UEContextSetupRequestIEs_t, ie, &in->protocolIEs.list, F1AP_ProtocolIE_ID_id_gNB_CU_UE_F1AP_ID, true); + F1AP_LIB_FIND_IE(F1AP_UEContextSetupRequestIEs_t, ie, &in->protocolIEs.list, F1AP_ProtocolIE_ID_id_SpCell_ID, true); + F1AP_LIB_FIND_IE(F1AP_UEContextSetupRequestIEs_t, ie, &in->protocolIEs.list, F1AP_ProtocolIE_ID_id_ServCellIndex, true); + F1AP_LIB_FIND_IE(F1AP_UEContextSetupRequestIEs_t, ie, &in->protocolIEs.list, F1AP_ProtocolIE_ID_id_CUtoDURRCInformation, true); + + for (int i = 0; i < in->protocolIEs.list.count; ++i) { + ie = in->protocolIEs.list.array[i]; + AssertError(ie != NULL, return false, "in->protocolIEs.list.array[i] is NULL"); + switch (ie->id) { + case F1AP_ProtocolIE_ID_id_gNB_CU_UE_F1AP_ID: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_GNB_CU_UE_F1AP_ID); + out->gNB_CU_ue_id = ie->value.choice.GNB_CU_UE_F1AP_ID; + break; + case F1AP_ProtocolIE_ID_id_gNB_DU_UE_F1AP_ID: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_GNB_DU_UE_F1AP_ID); + _F1_MALLOC(out->gNB_DU_ue_id, ie->value.choice.GNB_DU_UE_F1AP_ID); + break; + case F1AP_ProtocolIE_ID_id_SpCell_ID: { + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_NRCGI); + const F1AP_NRCGI_t *nrcgi = &ie->value.choice.NRCGI; + PLMNID_TO_MCC_MNC(&nrcgi->pLMN_Identity, out->plmn.mcc, out->plmn.mnc, out->plmn.mnc_digit_length); + BIT_STRING_TO_NR_CELL_IDENTITY(&nrcgi->nRCellIdentity, out->nr_cellid); + } break; + case F1AP_ProtocolIE_ID_id_ServCellIndex: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_ServCellIndex); + out->servCellIndex = ie->value.choice.ServCellIndex; + break; + case F1AP_ProtocolIE_ID_id_CUtoDURRCInformation: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_CUtoDURRCInformation); + _F1_CHECK_EXP(decode_cu_to_du_rrc_info(&out->cu_to_du_rrc_info, &ie->value.choice.CUtoDURRCInformation)); + break; + case F1AP_ProtocolIE_ID_id_SRBs_ToBeSetup_List: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_SRBs_ToBeSetup_List); + _F1_CHECK_EXP(decode_srbs_to_setup(&ie->value.choice.SRBs_ToBeSetup_List, &out->srbs_len, &out->srbs)); + break; + case F1AP_ProtocolIE_ID_id_DRBs_ToBeSetup_List: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_DRBs_ToBeSetup_List); + { + // gNB-DU UE Aggregate Maximum Bit Rate Uplink is C-ifDRBSetup + // below, check that gNB-DU UE AMBR UL IE is present if we have DRB + // setup, as required by the spec, or return false otherwise (like in + // the beginning of the function) + F1AP_UEContextSetupRequestIEs_t *check_ie; + F1AP_LIB_FIND_IE(F1AP_UEContextSetupRequestIEs_t, check_ie, &in->protocolIEs.list, F1AP_ProtocolIE_ID_id_GNB_DU_UE_AMBR_UL, true); + } + _F1_CHECK_EXP(decode_drbs_to_setup(&ie->value.choice.DRBs_ToBeSetup_List, &out->drbs_len, &out->drbs)); + break; + case F1AP_ProtocolIE_ID_id_RRCContainer: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_RRCContainer); + out->rrc_container = malloc_or_fail(sizeof(*out->rrc_container)); + { + OCTET_STRING_t *os = &ie->value.choice.RRCContainer; + *out->rrc_container = create_byte_array(os->size, os->buf); + } + break; + case F1AP_ProtocolIE_ID_id_GNB_DU_UE_AMBR_UL: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_UEContextSetupRequestIEs__value_PR_BitRate); + out->gnb_du_ue_agg_mbr_ul = malloc_or_fail(sizeof(*out->gnb_du_ue_agg_mbr_ul)); + asn_INTEGER2uint64(&ie->value.choice.BitRate, out->gnb_du_ue_agg_mbr_ul); + break; + default: + PRINT_ERROR("F1AP_ProtocolIE_ID_id %ld unknown, skipping\n", ie->id); + break; + } + } + + return true; +} + +/** + * @brief F1 UE Context Setup Request deep copy + */ +f1ap_ue_context_setup_req_t cp_ue_context_setup_req(const f1ap_ue_context_setup_req_t *orig) +{ + /* copy all mandatory fields that are not dynamic memory */ + f1ap_ue_context_setup_req_t cp = { + .gNB_CU_ue_id = orig->gNB_CU_ue_id, + .plmn = orig->plmn, + .nr_cellid = orig->nr_cellid, + .servCellIndex = orig->servCellIndex, + .cu_to_du_rrc_info = cp_cu_to_du_rrc_info(&orig->cu_to_du_rrc_info), + }; + if (orig->gNB_DU_ue_id) + _F1_MALLOC(cp.gNB_DU_ue_id, *orig->gNB_DU_ue_id); + if (orig->srbs_len > 0 && orig->srbs) { + cp.srbs = calloc_or_fail(orig->srbs_len, sizeof(*cp.srbs)); + cp.srbs_len = orig->srbs_len; + for (int i = 0; i < cp.srbs_len; ++i) + cp.srbs[i] = cp_srb_to_setup(&orig->srbs[i]); + } + if (orig->drbs_len > 0 && orig->drbs) { + cp.drbs = calloc_or_fail(orig->drbs_len, sizeof(*cp.drbs)); + cp.drbs_len = orig->drbs_len; + for (int i = 0; i < cp.drbs_len; ++i) + cp.drbs[i] = cp_drb_to_setup(&orig->drbs[i]); + } + CP_OPT_BYTE_ARRAY(cp.rrc_container, orig->rrc_container); + if (orig->gnb_du_ue_agg_mbr_ul) + _F1_MALLOC(cp.gnb_du_ue_agg_mbr_ul, *orig->gnb_du_ue_agg_mbr_ul); + return cp; +} + +/** + * @brief F1 UE Context Setup Request equality check + */ +bool eq_ue_context_setup_req(const f1ap_ue_context_setup_req_t *a, const f1ap_ue_context_setup_req_t *b) +{ + _F1_EQ_CHECK_INT(a->gNB_CU_ue_id, b->gNB_CU_ue_id); + _F1_EQ_CHECK_OPTIONAL_IE(a, b, gNB_DU_ue_id, _F1_EQ_CHECK_INT); + _F1_CHECK_EXP(eq_f1ap_plmn(&a->plmn, &b->plmn)); + _F1_EQ_CHECK_LONG(a->nr_cellid, b->nr_cellid); + _F1_EQ_CHECK_INT(a->servCellIndex, b->servCellIndex); + _F1_CHECK_EXP(eq_cu_to_du_rrc_info(&a->cu_to_du_rrc_info, &b->cu_to_du_rrc_info)); + + _F1_EQ_CHECK_INT(a->srbs_len, b->srbs_len); + _F1_CHECK_EXP(a->srbs_len == 0 || (a->srbs && b->srbs)); + for (int i = 0; i < a->srbs_len; ++i) + _F1_CHECK_EXP(eq_srb_to_setup(&a->srbs[i], &b->srbs[i])); + + _F1_EQ_CHECK_INT(a->drbs_len, b->drbs_len); + _F1_CHECK_EXP(a->drbs_len == 0 || (a->drbs && b->drbs)); + for (int i = 0; i < a->drbs_len; ++i) + _F1_CHECK_EXP(eq_drb_to_setup(&a->drbs[i], &b->drbs[i])); + + _F1_EQ_CHECK_OPTIONAL_IE(a, b, rrc_container, eq_ba); + + _F1_EQ_CHECK_OPTIONAL_IE(a, b, gnb_du_ue_agg_mbr_ul, _F1_EQ_CHECK_LONG); + return true; +} + +/** + * @brief Free Allocated F1 UE Context Setup Request + */ +void free_ue_context_setup_req(f1ap_ue_context_setup_req_t *req) +{ + free(req->gNB_DU_ue_id); + free_cu_to_du_rrc_info(&req->cu_to_du_rrc_info); + for (int i = 0; i < req->srbs_len; ++i) + free_srb_to_setup(&req->srbs[i]); + free(req->srbs); + for (int i = 0; i < req->drbs_len; ++i) + free_drb_to_setup(&req->drbs[i]); + free(req->drbs); + FREE_OPT_BYTE_ARRAY(req->rrc_container); + free(req->gnb_du_ue_agg_mbr_ul); +} diff --git a/openair2/F1AP/lib/f1ap_ue_context.h b/openair2/F1AP/lib/f1ap_ue_context.h new file mode 100644 index 0000000000..947273b7a8 --- /dev/null +++ b/openair2/F1AP/lib/f1ap_ue_context.h @@ -0,0 +1,36 @@ +/* + * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The OpenAirInterface Software Alliance licenses this file to You under + * the OAI Public License, Version 1.1 (the "License"); you may not use this file + * except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.openairinterface.org/?page_id=698 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------------------- + * For more information about the OpenAirInterface (OAI) Software Alliance: + * contact@openairinterface.org + */ + +#ifndef F1AP_UE_CONTEXT_SETUP_REQ_H_ +#define F1AP_UE_CONTEXT_SETUP_REQ_H_ + +#include +#include "f1ap_messages_types.h" + +struct F1AP_F1AP_PDU; + +struct F1AP_F1AP_PDU *encode_ue_context_setup_req(const f1ap_ue_context_setup_req_t *msg); +bool decode_ue_context_setup_req(const struct F1AP_F1AP_PDU *pdu, f1ap_ue_context_setup_req_t *out); +f1ap_ue_context_setup_req_t cp_ue_context_setup_req(const f1ap_ue_context_setup_req_t *orig); +bool eq_ue_context_setup_req(const f1ap_ue_context_setup_req_t *a, const f1ap_ue_context_setup_req_t *b); +void free_ue_context_setup_req(f1ap_ue_context_setup_req_t *req); + +#endif /* F1AP_UE_CONTEXT_SETUP_REQ_H_ */ diff --git a/openair2/F1AP/tests/CMakeLists.txt b/openair2/F1AP/tests/CMakeLists.txt index a73334051a..396e86d513 100644 --- a/openair2/F1AP/tests/CMakeLists.txt +++ b/openair2/F1AP/tests/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(f1ap_lib_test f1ap_lib_test.c) add_dependencies(tests f1ap_lib_test) add_test(NAME f1ap_lib_test COMMAND f1ap_lib_test) -target_link_libraries(f1ap_lib_test PRIVATE f1ap_lib) +target_link_libraries(f1ap_lib_test PRIVATE f1ap_lib ds) target_link_libraries(f1ap_lib_test PRIVATE asn1_f1ap) target_include_directories(f1ap_lib_test PRIVATE ${CMAKE_SOURCE_DIR}) diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 57a503d8ab..a06bd28564 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "common/utils/assertions.h" #include "common/utils/utils.h" @@ -38,8 +39,10 @@ #include "f1ap_messages_types.h" #include "F1AP_F1AP-PDU.h" +#include "lib/f1ap_lib_common.h" #include "lib/f1ap_rrc_message_transfer.h" #include "lib/f1ap_interface_management.h" +#include "lib/f1ap_ue_context.h" void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) { @@ -718,6 +721,171 @@ static void test_f1ap_cu_configuration_update_acknowledge(void) // No free needed } +static byte_array_t get_test_ba(const char *s) +{ + byte_array_t ba = { + .len = strlen(s) + 1, /* binary data: needs to include null-byte of strdup() */ + .buf = (uint8_t *) strdup(s), + }; + return ba; +} + +static byte_array_t *get_malloced_test_ba(const char *s) +{ + byte_array_t *ba = calloc_or_fail(1, sizeof(*ba)); + *ba = get_test_ba(s); + return ba; +} + +static void test_f1ap_ue_context_setup_request() +{ + plmn_id_t plmn = { .mcc = 001, .mnc = 01, .mnc_digit_length = 2 }; + f1ap_ue_context_setup_req_t orig = { + .gNB_CU_ue_id = 12, + //.gNB_DU_ue_id below + .plmn = plmn, + .nr_cellid = 132, + .servCellIndex = 1, + //.cu_to_du_rrc_info below + //.srbs below + //.drbs below + //gnb_du_ue_agg_mbr_ul below + }; + _F1_MALLOC(orig.gNB_DU_ue_id, 13); + orig.cu_to_du_rrc_info.cg_configinfo = get_malloced_test_ba("CG-CONFIGINFO"); + orig.cu_to_du_rrc_info.ue_cap = get_malloced_test_ba("UE CAP"); + orig.cu_to_du_rrc_info.meas_config = get_malloced_test_ba("UE MEASUREMENT CONFIGURATION"); + orig.cu_to_du_rrc_info.ho_prep_info = get_malloced_test_ba("PREPARATION DATA FOR HANDOVER"); + orig.srbs_len = 2; + orig.srbs = calloc_or_fail(orig.srbs_len, sizeof(*orig.srbs)); + for (int i = 0; i < orig.srbs_len; ++i) + orig.srbs[i].id = 1 + i; + + orig.drbs_len = 2; + orig.drbs = calloc_or_fail(orig.drbs_len, sizeof(*orig.drbs)); + f1ap_drb_to_setup_t *drb1 = &orig.drbs[0]; + drb1->id = 4; + drb1->qos_choice = F1AP_QOS_CHOICE_NR; + f1ap_arp_t arp = { 1, MAY_TRIGGER_PREEMPTION, PREEMPTABLE, }; + drb1->nr.drb_qos.qos_type = NON_DYNAMIC; + drb1->nr.drb_qos.nondyn.fiveQI = 3; + drb1->nr.drb_qos.arp = arp; + drb1->nr.nssai = (nssai_t) {.sst = 1, .sd = 123}; + drb1->nr.flows_len = 1; + f1ap_drb_flows_mapped_t *flow = drb1->nr.flows = calloc_or_fail(drb1->nr.flows_len, sizeof(*flow)); + flow->qfi = 2; + flow->param.qos_type = NON_DYNAMIC; + flow->param.nondyn.fiveQI = 3; + flow->param.arp = arp; + drb1->up_ul_tnl_len = 1; + inet_pton(AF_INET, "192.168.40.23", &drb1->up_ul_tnl[0].tl_address); + drb1->up_ul_tnl[0].teid = 0x11223344; + drb1->rlc_mode = F1AP_RLC_MODE_UM_BIDIR; + _F1_MALLOC(drb1->dl_pdcp_sn_len, F1AP_PDCP_SN_18B); + _F1_MALLOC(drb1->ul_pdcp_sn_len, F1AP_PDCP_SN_18B); + + f1ap_drb_to_setup_t *drb2 = &orig.drbs[1]; + drb2->id = 12; + drb2->qos_choice = F1AP_QOS_CHOICE_NR; + drb2->nr.drb_qos.qos_type = DYNAMIC; + f1ap_dynamic_5qi_t *dyn = &drb2->nr.drb_qos.dyn; + dyn->prio = 4; + dyn->pdb = 1000; + dyn->per.scalar = 4; + dyn->per.exponent = 8; + _F1_MALLOC(dyn->delay_critical, true); + _F1_MALLOC(dyn->avg_win, 3000); + f1ap_arp_t arp2 = { 13, SHALL_NOT_TRIGGER_PREEMPTION, NOT_PREEMPTABLE, }; + drb2->nr.drb_qos.arp = arp2; + drb2->nr.nssai = (nssai_t) {.sst = 2, .sd = 0xffffff}; + drb2->nr.flows_len = 2; + f1ap_drb_flows_mapped_t *flow2 = drb2->nr.flows = calloc_or_fail(drb2->nr.flows_len, sizeof(*flow2)); + flow2[0].qfi = 4; + flow2[0].param.qos_type = DYNAMIC; + flow2[0].param.dyn.prio = 4; + flow2[0].param.dyn.pdb = 999; + flow2[0].param.dyn.per.scalar = 4; + flow2[0].param.dyn.per.exponent = 8; + flow2[0].param.arp = arp2; + flow2[1].qfi = 5; + flow2[1].param.qos_type = NON_DYNAMIC; + flow2[1].param.nondyn.fiveQI = 3; + flow2[1].param.arp = arp; + drb2->up_ul_tnl_len = 2; + inet_pton(AF_INET, "10.0.0.2", &drb2->up_ul_tnl[0].tl_address); + drb2->up_ul_tnl[0].teid = 0x1234; + inet_pton(AF_INET, "10.0.0.2", &drb2->up_ul_tnl[1].tl_address); + drb2->up_ul_tnl[1].teid = 0x1235; + drb2->rlc_mode = F1AP_RLC_MODE_AM; + _F1_MALLOC(drb2->dl_pdcp_sn_len, F1AP_PDCP_SN_12B); + _F1_MALLOC(drb2->ul_pdcp_sn_len, F1AP_PDCP_SN_12B); + + orig.rrc_container = get_malloced_test_ba("RRC container"); + + _F1_MALLOC(orig.gnb_du_ue_agg_mbr_ul, 5LL * 1000 * 1000 * 1000); + + F1AP_F1AP_PDU_t *f1enc = encode_ue_context_setup_req(&orig); + F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); + f1ap_msg_free(f1enc); + + f1ap_ue_context_setup_req_t decoded = {0}; + bool ret = decode_ue_context_setup_req(f1dec, &decoded); + AssertFatal(ret, "decode_ue_context_setup_req(): could not decode message\n"); + f1ap_msg_free(f1dec); + + ret = eq_ue_context_setup_req(&orig, &decoded); + AssertFatal(ret, "eq_ue_context_setup_req(): decoded message doesn't match\n"); + char ip_ret[100]; + inet_ntop(AF_INET, &decoded.drbs[0].up_ul_tnl[0].tl_address, ip_ret, sizeof(ip_ret)); + printf("%s\n", ip_ret); + inet_ntop(AF_INET, &decoded.drbs[1].up_ul_tnl[0].tl_address, ip_ret, sizeof(ip_ret)); + printf("%s\n", ip_ret); + free_ue_context_setup_req(&decoded); + + f1ap_ue_context_setup_req_t cp = cp_ue_context_setup_req(&orig); + ret = eq_ue_context_setup_req(&orig, &cp); + AssertFatal(ret, "eq_ue_context_setup_req(): copied message doesn't match\n"); + free_ue_context_setup_req(&orig); + free_ue_context_setup_req(&cp); + + printf("%s() successful\n", __func__); +} + +static void test_f1ap_ue_context_setup_request_simple() +{ + plmn_id_t plmn = { .mcc = 001, .mnc = 01, .mnc_digit_length = 2 }; + f1ap_ue_context_setup_req_t orig = { + .gNB_CU_ue_id = 12, + .plmn = plmn, + .nr_cellid = 132, + .servCellIndex = 1, + //.cu_to_du_rrc_info: all fields optional, intentionally left empty + // rest is optional and intentionally left empty + // .gnb_du_ue_agg_mbr_ul is C-ifDRBSetup => no DRB => no IE + }; + F1AP_F1AP_PDU_t *f1enc = encode_ue_context_setup_req(&orig); + F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); + f1ap_msg_free(f1enc); + + f1ap_ue_context_setup_req_t decoded = {0}; + bool ret = decode_ue_context_setup_req(f1dec, &decoded); + AssertFatal(ret, "decode_ue_context_setup_req(): could not decode message\n"); + f1ap_msg_free(f1dec); + + ret = eq_ue_context_setup_req(&orig, &decoded); + AssertFatal(ret, "eq_ue_context_setup_req(): decoded message doesn't match\n"); + free_ue_context_setup_req(&decoded); + + f1ap_ue_context_setup_req_t cp = cp_ue_context_setup_req(&orig); + ret = eq_ue_context_setup_req(&orig, &cp); + AssertFatal(ret, "eq_ue_context_setup_req(): copied message doesn't match\n"); + free_ue_context_setup_req(&orig); + free_ue_context_setup_req(&cp); + + printf("%s() successful\n", __func__); +} + + int main() { test_initial_ul_rrc_message_transfer(); @@ -733,5 +901,7 @@ int main() test_f1ap_cu_configuration_update(); test_f1ap_cu_configuration_update_acknowledge(); test_f1ap_du_configuration_update_acknowledge(); + test_f1ap_ue_context_setup_request(); + test_f1ap_ue_context_setup_request_simple(); return 0; }