diff --git a/CMakeLists.txt b/CMakeLists.txt index f0b080f5c8..70404701a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -434,6 +434,7 @@ add_library(ngap ${NGAP_DIR}/ngap_gNB_handlers.c ${NGAP_DIR}/ngap_gNB_itti_messaging.c ${NGAP_DIR}/ngap_gNB_management_procedures.c + ${NGAP_DIR}/ngap_gNB_paging.c ${NGAP_DIR}/ngap_gNB_nas_procedures.c ${NGAP_DIR}/ngap_gNB_pdu_session_management.c ${NGAP_DIR}/ngap_gNB_nnsf.c @@ -443,6 +444,9 @@ add_library(ngap ) target_link_libraries(ngap PUBLIC asn1_ngap) target_link_libraries(ngap PRIVATE asn1_lte_rrc_hdrs asn1_nr_rrc_hdrs) +if(ENABLE_TESTS) + target_compile_definitions(ngap PRIVATE ENABLE_TESTS) +endif() #M2AP ############## diff --git a/openair2/COMMON/ngap_messages_types.h b/openair2/COMMON/ngap_messages_types.h index 08d93c6b10..02069d056e 100644 --- a/openair2/COMMON/ngap_messages_types.h +++ b/openair2/COMMON/ngap_messages_types.h @@ -32,7 +32,6 @@ #include "common/5g_platform_types.h" #include "common/platform_constants.h" #include "common/platform_types.h" -#include "common/5g_platform_types.h" #include "s1ap_messages_types.h" #include "ds/byte_array.h" #include "utils.h" @@ -89,39 +88,46 @@ #define NGAP_MAX_NB_AMF_IP_ADDRESS 10 -#define NGAP_MAX_NO_TAI_PAGING 16 // 9.2.4.1 3GPP TS 38.413 - /* Security key length used within gNB * Even if only 16 bytes will be effectively used, * the key length is 32 bytes (256 bits) */ #define SECURITY_KEY_LENGTH 32 -typedef enum ngap_paging_drx_e { - NGAP_PAGING_DRX_32 = 0x0, - NGAP_PAGING_DRX_64 = 0x1, - NGAP_PAGING_DRX_128 = 0x2, - NGAP_PAGING_DRX_256 = 0x3 -} ngap_paging_drx_t; +#define NGAP_MAX_NO_TAI_PAGING 16 // 9.2.4.1 3GPP TS 38.413 -/* Lower value codepoint - * indicates higher priority. - */ -typedef enum ngap_paging_priority_s { - NGAP_PAGING_PRIO_LEVEL1 = 0, - NGAP_PAGING_PRIO_LEVEL2 = 1, - NGAP_PAGING_PRIO_LEVEL3 = 2, - NGAP_PAGING_PRIO_LEVEL4 = 3, - NGAP_PAGING_PRIO_LEVEL5 = 4, - NGAP_PAGING_PRIO_LEVEL6 = 5, - NGAP_PAGING_PRIO_LEVEL7 = 6, - NGAP_PAGING_PRIO_LEVEL8 = 7 -} ngap_paging_priority_t; +/* Paging DRX values (3GPP TS 38.413) */ +#define FOREACH_PAGING_DRX(DRX_DEF) \ + DRX_DEF(NGAP_PAGING_DRX_32, 0x0) \ + DRX_DEF(NGAP_PAGING_DRX_64, 0x1) \ + DRX_DEF(NGAP_PAGING_DRX_128, 0x2) \ + DRX_DEF(NGAP_PAGING_DRX_256, 0x3) -typedef enum ngap_cn_domain_s { - NGAP_CN_DOMAIN_PS = 1, - NGAP_CN_DOMAIN_CS = 2 -} ngap_cn_domain_t; +/* Lower value codepoint indicates higher priority (3GPP TS 38.413) */ +#define FOREACH_PAGING_PRIORITY(PRIO_DEF) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL1, 0) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL2, 1) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL3, 2) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL4, 3) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL5, 4) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL6, 5) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL7, 6) \ + PRIO_DEF(NGAP_PAGING_PRIO_LEVEL8, 7) + +/* Paging Origin (9.3.3.22 of 3GPP TS 38.413) */ +#define FOREACH_PAGING_ORIGIN(ORIGIN_DEF) ORIGIN_DEF(NGAP_PAGING_ORIGIN_NON_3GPP, 0) + +static const text_info_t paging_drx_text[] = {FOREACH_PAGING_DRX(TO_TEXT)}; + +static const text_info_t paging_prio_text[] = {FOREACH_PAGING_PRIORITY(TO_TEXT)}; + +static const text_info_t paging_origin_text[] = {FOREACH_PAGING_ORIGIN(TO_TEXT)}; + +typedef enum { FOREACH_PAGING_DRX(TO_ENUM) } ngap_paging_drx_t; + +typedef enum { FOREACH_PAGING_PRIORITY(TO_ENUM) } ngap_paging_priority_t; + +typedef enum { FOREACH_PAGING_ORIGIN(TO_ENUM) } ngap_paging_origin_t; typedef struct ngap_net_ip_address_s { unsigned ipv4:1; @@ -171,10 +177,6 @@ typedef struct fiveg_s_tmsi_s { uint32_t m_tmsi; } fiveg_s_tmsi_t; -typedef struct ngap_ue_paging_identity_s { - fiveg_s_tmsi_t s_tmsi; -} ngap_ue_paging_identity_t; - typedef enum ngap_ue_identities_presenceMask_e { NGAP_UE_IDENTITIES_FiveG_s_tmsi = 1 << 1, NGAP_UE_IDENTITIES_guami = 1 << 2, @@ -797,29 +799,6 @@ typedef struct ngap_initial_context_setup_req_s { byte_array_t nas_pdu; } ngap_initial_context_setup_req_t; - -typedef struct ngap_paging_ind_s { - /* UE paging identity */ - ngap_ue_paging_identity_t ue_paging_identity; - - /* Indicates origin of paging */ - ngap_cn_domain_t cn_domain; - - /* PLMN_identity in TAI of Paging*/ - plmn_id_t plmn_identity[NGAP_MAX_NO_TAI_PAGING]; - - /* TAC in TAIList of Paging*/ - int16_t tac[NGAP_MAX_NO_TAI_PAGING]; - - /* size of TAIList*/ - int16_t tai_size; - - /* Optional fields */ - ngap_paging_drx_t paging_drx; - - ngap_paging_priority_t paging_priority; -} ngap_paging_ind_t; - typedef struct ngap_pdusession_setup_req_s { /* UE id for initial connection to NGAP */ uint32_t gNB_ue_ngap_id; @@ -946,6 +925,45 @@ typedef struct ngap_pdusession_release_resp_s { pdusession_release_t pdusession_release[NGAP_MAX_PDU_SESSION]; } ngap_pdusession_release_resp_t; +/** NG PAGING PROCEDURES (9.2.4. of 3GPP TS 38.413) */ + +typedef struct { + plmn_id_t plmn; + uint16_t tac; +} nr_tai_t; + +typedef struct ngap_ue_paging_identity_s { + fiveg_s_tmsi_t s_tmsi; +} ngap_ue_paging_identity_t; + +/** 9.3.1.72 Paging Attempt Information (3GPP TS 38.413) */ +typedef struct { + /* Paging Attempt Count */ + uint8_t paging_attempt_count; + /* Intended Number of Paging Attempts */ + uint8_t intended_paging_attempts; +} ngap_paging_attempt_info_t; + +/** 9.2.4.1 3GPP TS 38.413 */ +typedef struct { + /* UE paging identity */ + ngap_ue_paging_identity_t ue_paging_identity; + + /* TAI List for Paging */ + nr_tai_t tai_list[NGAP_MAX_NO_TAI_PAGING]; + int16_t n_tai; + + /* Optional fields */ + ngap_paging_drx_t *paging_drx; + ngap_paging_priority_t *paging_priority; + /* UE Radio Capability for Paging (optional) */ + byte_array_t *ue_radio_capability; + /* Paging Origin (optional) */ + ngap_paging_origin_t *origin; + /* Assistance Data for Paging (optional) */ + ngap_paging_attempt_info_t *paging_attempt_info; +} ngap_paging_ind_t; + /** 9.2.3.14 Uplink RAN Status Transfer (3GPP TS 38.413) * COUNT value used for both UL and DL PDCP SN + HFN (12-bit or 18-bit SN) */ diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 8ebd0b0a37..d86b77a188 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1632,19 +1632,20 @@ int rrc_gNB_process_NGAP_PDUSESSION_RELEASE_COMMAND(ngap_pdusession_release_comm int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) { ngap_paging_ind_t *msg = &NGAP_PAGING_IND(msg_p); - for (uint16_t tai_size = 0; tai_size < msg->tai_size; tai_size++) { - plmn_id_t *p = &msg->plmn_identity[tai_size]; + for (uint16_t tai_idx = 0; tai_idx < msg->n_tai; tai_idx++) { + nr_tai_t *tai = &msg->tai_list[tai_idx]; + plmn_id_t *p = &tai->plmn; LOG_I(NR_RRC, "[gNB %ld] TAI List for Paging: MCC=%03d, MNC=%0*d, TAC=%d\n", instance, p->mcc, p->mnc_digit_length, p->mnc, - msg->tac[tai_size]); + tai->tac); nr_rrc_config_t *req = &RC.nrrrc[instance]->configuration; for (uint8_t j = 0; j < req->num_plmn; j++) { plmn_id_t *req_plmn = &req->plmn[j]; - if (req_plmn->mcc == p->mcc && req_plmn->mnc == p->mnc && req->tac == msg->tac[tai_size]) { + if (req_plmn->mcc == p->mcc && req_plmn->mnc == p->mnc && req->tac == tai->tac) { for (uint8_t CC_id = 0; CC_id < MAX_NUM_CCs; CC_id++) { AssertFatal(false, "to be implemented properly\n"); if (NODE_IS_CU(RC.nrrrc[instance]->node_type)) { @@ -1669,7 +1670,7 @@ int rrc_gNB_process_PAGING_IND(MessageDef *msg_p, instance_t instance) F1AP_PAGING_IND(m).nr_cellid = cell->info.cell_id; F1AP_PAGING_IND(m).ueidentityindexvalue = (uint16_t)(msg->ue_paging_identity.s_tmsi.m_tmsi % 1024); F1AP_PAGING_IND(m).fiveg_s_tmsi = msg->ue_paging_identity.s_tmsi.m_tmsi; - F1AP_PAGING_IND(m).paging_drx = msg->paging_drx; + F1AP_PAGING_IND(m).paging_drx = msg->paging_drx ? (uint8_t)*msg->paging_drx : 0; LOG_E(F1AP, "ueidentityindexvalue %u fiveg_s_tmsi %ld paging_drx %u\n", F1AP_PAGING_IND (m).ueidentityindexvalue, F1AP_PAGING_IND (m).fiveg_s_tmsi, F1AP_PAGING_IND (m).paging_drx); itti_send_msg_to_task(TASK_CU_F1, instance, m); } else { diff --git a/openair3/NGAP/CMakeLists.txt b/openair3/NGAP/CMakeLists.txt index 7537bd661d..658bfeee5a 100644 --- a/openair3/NGAP/CMakeLists.txt +++ b/openair3/NGAP/CMakeLists.txt @@ -1 +1,5 @@ add_subdirectory(MESSAGES) + +if(ENABLE_TESTS) + add_subdirectory(tests) +endif() diff --git a/openair3/NGAP/ngap_common.c b/openair3/NGAP/ngap_common.c index 3923586989..c14b6c80d8 100644 --- a/openair3/NGAP/ngap_common.c +++ b/openair3/NGAP/ngap_common.c @@ -27,6 +27,7 @@ * \version 0.1 */ +#include #include #include #include "conversions.h" @@ -331,3 +332,14 @@ byte_array_t encode_ngap_pdusession_setup_response_transfer(const pdusession_set out.len = res.result.encoded; return out; } + +bool eq_ngap_plmn(const plmn_id_t *a, const plmn_id_t *b) +{ + if (a == NULL || b == NULL) { + return false; + } + _EQ_CHECK_INT(a->mcc, b->mcc); + _EQ_CHECK_INT(a->mnc, b->mnc); + _EQ_CHECK_INT(a->mnc_digit_length, b->mnc_digit_length); + return true; +} diff --git a/openair3/NGAP/ngap_common.h b/openair3/NGAP/ngap_common.h index c2426a7b8a..64b073720e 100644 --- a/openair3/NGAP/ngap_common.h +++ b/openair3/NGAP/ngap_common.h @@ -63,5 +63,7 @@ void *decode_pdusession_transfer(const asn_TYPE_descriptor_t *td, const OCTET_ST bool decodePDUSessionResourceSetup(pdusession_transfer_t *out, const OCTET_STRING_t in); byte_array_t encode_ngap_pdusession_setup_response_transfer(const pdusession_setup_t *pdusession); +bool eq_ngap_plmn(const plmn_id_t *a, const plmn_id_t *b); + /** @}*/ #endif /* NGAP_COMMON_H_ */ diff --git a/openair3/NGAP/ngap_gNB_handlers.c b/openair3/NGAP/ngap_gNB_handlers.c index 6e48505ff4..fc87bd2d45 100644 --- a/openair3/NGAP/ngap_gNB_handlers.c +++ b/openair3/NGAP/ngap_gNB_handlers.c @@ -49,6 +49,7 @@ #include "ngap_gNB_mobility_management.h" #include "ngap_gNB_pdu_session_management.h" #include "ngap_gNB_nas_procedures.h" +#include "ngap_gNB_paging.h" #include "ngap_gNB_trace.h" #include "ngap_gNB_ue_context.h" #include "ngap_messages_types.h" @@ -1101,10 +1102,7 @@ static int ngap_gNB_handle_paging(sctp_assoc_t assoc_id, uint32_t stream, NGAP_N { ngap_gNB_amf_data_t *amf_desc_p = NULL; ngap_gNB_instance_t *ngap_gNB_instance = NULL; - NGAP_Paging_t *container; - NGAP_PagingIEs_t *ie; DevAssert(pdu != NULL); - container = &pdu->choice.initiatingMessage->value.choice.Paging; // received Paging Message from AMF NGAP_DEBUG("[SCTP %u] Received Paging Message From AMF\n",assoc_id); @@ -1130,67 +1128,20 @@ static int ngap_gNB_handle_paging(sctp_assoc_t assoc_id, uint32_t stream, NGAP_N } MessageDef *message_p = itti_alloc_new_message(TASK_NGAP, 0, NGAP_PAGING_IND); - ngap_paging_ind_t * msg=&NGAP_PAGING_IND(message_p); - memset(msg, 0, sizeof(*msg)); + ngap_paging_ind_t *msg = &NGAP_PAGING_IND(message_p); - /* convert NGAP_PagingIEs_t to ngap_paging_ind_t */ - /* id-UEIdentityIndexValue : convert UE Identity Index value */ - NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UEPagingIdentity, true); - - struct NGAP_FiveG_S_TMSI *fiveG_S_TMSI = ie->value.choice.UEPagingIdentity.choice.fiveG_S_TMSI; - OCTET_STRING_TO_INT16(&fiveG_S_TMSI->aMFSetID, msg->ue_paging_identity.s_tmsi.amf_set_id); - OCTET_STRING_TO_INT8(&fiveG_S_TMSI->aMFPointer, msg->ue_paging_identity.s_tmsi.amf_pointer); - OCTET_STRING_TO_INT32(&fiveG_S_TMSI->fiveG_TMSI, msg->ue_paging_identity.s_tmsi.m_tmsi); - - NGAP_DEBUG("[SCTP %u] Received Paging Identity amf_set_id %d, amf_pointer %d, m_tmsi %d\n", - assoc_id, - msg->ue_paging_identity.s_tmsi.amf_set_id, - msg->ue_paging_identity.s_tmsi.amf_pointer, - msg->ue_paging_identity.s_tmsi.m_tmsi); - - msg->paging_drx = NGAP_PAGING_DRX_256; - /* id-pagingDRX */ - NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_PagingDRX, false); - - /* optional */ - if (ie) { - msg->paging_drx = ie->value.choice.PagingDRX; - } else { - msg->paging_drx = NGAP_PAGING_DRX_256; + /* Decode NGAP Paging message */ + if (!decode_ng_paging(msg, pdu)) { + NGAP_ERROR("[SCTP %u] Failed to decode NGAP Paging message\n", assoc_id); + free_ng_paging(msg); + itti_free(TASK_NGAP, message_p); + return -1; } - /* id-TAIList */ - NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, - NGAP_ProtocolIE_ID_id_TAIListForPaging, true); + /* send message to RRC */ + itti_send_msg_to_task(TASK_RRC_GNB, ngap_gNB_instance->instance, message_p); - NGAP_INFO("[SCTP %u] Received Paging taiList For Paging: count %d\n", assoc_id, ie->value.choice.TAIListForPaging.list.count); - - for (int i = 0; i < ie->value.choice.TAIListForPaging.list.count; i++) { - NGAP_TAIListForPagingItem_t *item_p; - item_p = (NGAP_TAIListForPagingItem_t *)ie->value.choice.TAIListForPaging.list.array[i]; - TBCD_TO_MCC_MNC(&(item_p->tAI.pLMNIdentity), msg->plmn_identity[i].mcc, msg->plmn_identity[i].mnc, msg->plmn_identity[i].mnc_digit_length); - OCTET_STRING_TO_INT16(&(item_p->tAI.tAC), msg->tac[i]); - msg->tai_size++; - plmn_id_t *p = &msg->plmn_identity[i]; - LOG_D(NGAP, - "[SCTP %u] PLMN in TAI list for Paging: MCC=%03d, MNC=%0*d, TAC=%d\n", - assoc_id, - p->mcc, - p->mnc_digit_length, - p->mnc, - msg->tac[i]); - } - - //paging parameter values - NGAP_DEBUG("[SCTP %u] Received Paging parameters: Paging Identity amf_set_id %d amf_pointer %d m_tmsi %d paging_drx %d paging_priority %d\n",assoc_id, - msg->ue_paging_identity.s_tmsi.amf_set_id, - msg->ue_paging_identity.s_tmsi.amf_pointer, - msg->ue_paging_identity.s_tmsi.m_tmsi, - msg->paging_drx, msg->paging_priority); - /* send message to RRC */ - itti_send_msg_to_task(TASK_RRC_GNB, ngap_gNB_instance->instance, message_p); - - return 0; + return 0; } static bool decodePDUSessionResourceModify(pdusession_transfer_t *out, const OCTET_STRING_t in) diff --git a/openair3/NGAP/ngap_gNB_paging.c b/openair3/NGAP/ngap_gNB_paging.c new file mode 100644 index 0000000000..5a94c56fb8 --- /dev/null +++ b/openair3/NGAP/ngap_gNB_paging.c @@ -0,0 +1,330 @@ +/* + * 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 Software Alliance: + * contact@openairinterface.org + */ + +#include +#include "assertions.h" +#include "common/utils/utils.h" +#include "common/utils/ds/byte_array.h" +#include "conversions.h" +#include "ngap_common.h" +#include "ngap_gNB_paging.h" +#include "ngap_msg_includes.h" +#include "oai_asn1.h" + +/* Paging Attempt range (TS 38.413 §9.3.1.72) */ +#define NGAP_PAGING_ATTEMPT_MIN 1 +#define NGAP_PAGING_ATTEMPT_MAX 16 + +/** @brief Return text label at index in text_info array */ +static const char *text_info_at(int index, const text_info_t *info_array, size_t array_size) +{ + DevAssert(index >= 0 && index < array_size); + return info_array[index].text; +} + +/** @brief Encode NGAP Paging message (9.2.4.1 of 3GPP TS 38.413) + * @param msg Pointer to paging indication structure to encode + * @return Pointer to allocated NGAP_NGAP_PDU_t */ +NGAP_NGAP_PDU_t *encode_ng_paging(const ngap_paging_ind_t *msg) +{ + DevAssert(msg != NULL); + + NGAP_NGAP_PDU_t *pdu = calloc_or_fail(1, sizeof(NGAP_NGAP_PDU_t)); + + pdu->present = NGAP_NGAP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, head); + head->procedureCode = NGAP_ProcedureCode_id_Paging; + head->criticality = NGAP_Criticality_ignore; + head->value.present = NGAP_InitiatingMessage__value_PR_Paging; + NGAP_Paging_t *container = &head->value.choice.Paging; + + // UE Paging Identity (M) + asn1cSequenceAdd(container->protocolIEs.list, NGAP_PagingIEs_t, ie); + ie->id = NGAP_ProtocolIE_ID_id_UEPagingIdentity; + ie->criticality = NGAP_Criticality_reject; + ie->value.present = NGAP_PagingIEs__value_PR_UEPagingIdentity; + ie->value.choice.UEPagingIdentity.present = NGAP_UEPagingIdentity_PR_fiveG_S_TMSI; + asn1cCalloc(ie->value.choice.UEPagingIdentity.choice.fiveG_S_TMSI, fiveg_s_tmsi); + const fiveg_s_tmsi_t *s_tmsi = &msg->ue_paging_identity.s_tmsi; + AMF_SETID_TO_BIT_STRING(s_tmsi->amf_set_id, &fiveg_s_tmsi->aMFSetID); + AMF_POINTER_TO_BIT_STRING(s_tmsi->amf_pointer, &fiveg_s_tmsi->aMFPointer); + M_TMSI_TO_OCTET_STRING(s_tmsi->m_tmsi, &fiveg_s_tmsi->fiveG_TMSI); + + // TAI List for Paging (M) + asn1cSequenceAdd(container->protocolIEs.list, NGAP_PagingIEs_t, ie1); + ie1->id = NGAP_ProtocolIE_ID_id_TAIListForPaging; + ie1->criticality = NGAP_Criticality_reject; + ie1->value.present = NGAP_PagingIEs__value_PR_TAIListForPaging; + + for (int i = 0; i < msg->n_tai; i++) { + asn1cSequenceAdd(ie1->value.choice.TAIListForPaging.list, NGAP_TAIListForPagingItem_t, item); + NGAP_TAI_t *tai = &item->tAI; + const plmn_id_t *plmn = &msg->tai_list[i].plmn; + MCC_MNC_TO_TBCD(plmn->mcc, plmn->mnc, plmn->mnc_digit_length, &tai->pLMNIdentity); + INT24_TO_OCTET_STRING(msg->tai_list[i].tac, &tai->tAC); + } + + // Paging DRX (O) + if (msg->paging_drx != NULL) { + asn1cSequenceAdd(container->protocolIEs.list, NGAP_PagingIEs_t, ie2); + ie2->id = NGAP_ProtocolIE_ID_id_PagingDRX; + ie2->criticality = NGAP_Criticality_ignore; + ie2->value.present = NGAP_PagingIEs__value_PR_PagingDRX; + ie2->value.choice.PagingDRX = *msg->paging_drx; + } + + // Paging Priority (O) + if (msg->paging_priority != NULL) { + asn1cSequenceAdd(container->protocolIEs.list, NGAP_PagingIEs_t, ie3); + ie3->id = NGAP_ProtocolIE_ID_id_PagingPriority; + ie3->criticality = NGAP_Criticality_ignore; + ie3->value.present = NGAP_PagingIEs__value_PR_PagingPriority; + ie3->value.choice.PagingPriority = *msg->paging_priority; + } + + // UERadioCapabilityForPaging (O) + if (msg->ue_radio_capability != NULL && msg->ue_radio_capability->buf != NULL) { + const byte_array_t *ue_radio_cap_ba = msg->ue_radio_capability; + asn1cSequenceAdd(container->protocolIEs.list, NGAP_PagingIEs_t, ie4); + ie4->id = NGAP_ProtocolIE_ID_id_UERadioCapabilityForPaging; + ie4->criticality = NGAP_Criticality_ignore; + ie4->value.present = NGAP_PagingIEs__value_PR_UERadioCapabilityForPaging; + NGAP_UERadioCapabilityForPaging_t *ue_radio_cap = &ie4->value.choice.UERadioCapabilityForPaging; + // UERadioCapabilityForPaging is a SEQUENCE with optional OCTET_STRING fields (Encode as NR) + asn1cCalloc(ue_radio_cap->uERadioCapabilityForPagingOfNR, nr_field); + OCTET_STRING_fromBuf(nr_field, (const char *)ue_radio_cap_ba->buf, ue_radio_cap_ba->len); + } + + // PagingOrigin (O) + if (msg->origin != NULL) { + asn1cSequenceAdd(container->protocolIEs.list, NGAP_PagingIEs_t, ie5); + ie5->id = NGAP_ProtocolIE_ID_id_PagingOrigin; + ie5->criticality = NGAP_Criticality_ignore; + ie5->value.present = NGAP_PagingIEs__value_PR_PagingOrigin; + ie5->value.choice.PagingOrigin = *msg->origin; + } + + return pdu; +} + +/** @brief Free memory allocated for NGAP paging indication + * @param msg Pointer to paging indication structure to free */ +void free_ng_paging(ngap_paging_ind_t *msg) +{ + DevAssert(msg != NULL); + free(msg->paging_drx); + free(msg->paging_priority); + if (msg->ue_radio_capability != NULL) { + free_byte_array(*msg->ue_radio_capability); + free(msg->ue_radio_capability); + } + free(msg->origin); + free(msg->paging_attempt_info); +} + +/** @brief Decode NGAP Paging message (9.2.4.1 of 3GPP TS 38.413) + * @param out Pointer to output structure to fill + * @param pdu Pointer to NGAP PDU containing the Paging message + * @return true on success, false on failure */ +bool decode_ng_paging(ngap_paging_ind_t *out, const NGAP_NGAP_PDU_t *pdu) +{ + DevAssert(pdu != NULL); + DevAssert(out != NULL); + + if (pdu->present != NGAP_NGAP_PDU_PR_initiatingMessage) { + NGAP_ERROR("Invalid PDU present value for Paging message\n"); + return false; + } + + const NGAP_Paging_t *container = &pdu->choice.initiatingMessage->value.choice.Paging; + NGAP_PagingIEs_t *ie; + fiveg_s_tmsi_t *s_tmsi = &out->ue_paging_identity.s_tmsi; + + // Reset output structure + memset(out, 0, sizeof(*out)); + + // UE Paging Identity (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UEPagingIdentity, true); + + if (ie->value.choice.UEPagingIdentity.present == NGAP_UEPagingIdentity_PR_fiveG_S_TMSI) { + const struct NGAP_FiveG_S_TMSI *fiveG_S_TMSI = ie->value.choice.UEPagingIdentity.choice.fiveG_S_TMSI; + s_tmsi->amf_set_id = BIT_STRING_to_uint16(&fiveG_S_TMSI->aMFSetID); + s_tmsi->amf_pointer = BIT_STRING_to_uint8(&fiveG_S_TMSI->aMFPointer); + OCTET_STRING_TO_INT32(&fiveG_S_TMSI->fiveG_TMSI, s_tmsi->m_tmsi); + } else { + NGAP_ERROR("Unsupported UE Paging Identity type\n"); + return false; + } + + // TAI List for Paging (M) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_TAIListForPaging, true); + + // Validate TAI list count + int tai_count = ie->value.choice.TAIListForPaging.list.count; + if (tai_count > NGAP_MAX_NO_TAI_PAGING) { + NGAP_ERROR("Invalid TAI list count: %d (must be <= %d)\n", tai_count, NGAP_MAX_NO_TAI_PAGING); + return false; + } + + out->n_tai = 0; + for (int i = 0; i < tai_count; i++) { + const NGAP_TAIListForPagingItem_t *item_p = ie->value.choice.TAIListForPaging.list.array[i]; + DevAssert(item_p != NULL); + const NGAP_TAI_t *tai = &item_p->tAI; + nr_tai_t *tai_out = &out->tai_list[i]; + TBCD_TO_MCC_MNC(&tai->pLMNIdentity, tai_out->plmn.mcc, tai_out->plmn.mnc, tai_out->plmn.mnc_digit_length); + OCTET_STRING_TO_INT24(&tai->tAC, tai_out->tac); + NGAP_DEBUG("TAI[%d] for paging: MCC=%03d, MNC=%0*d, TAC=%d\n", + i, + tai_out->plmn.mcc, + tai_out->plmn.mnc_digit_length, + tai_out->plmn.mnc, + tai_out->tac); + out->n_tai++; + } + + // Paging DRX (O) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_PagingDRX, false); + if (ie != NULL) { + out->paging_drx = malloc_or_fail(sizeof(ngap_paging_drx_t)); + *out->paging_drx = ie->value.choice.PagingDRX; + const char *s = text_info_at(*out->paging_drx, paging_drx_text, sizeofArray(paging_drx_text)); + NGAP_DEBUG("Decoded Paging DRX: %s", s); + } + + // Paging Priority (O) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_PagingPriority, false); + if (ie != NULL) { + out->paging_priority = malloc_or_fail(sizeof(ngap_paging_priority_t)); + *out->paging_priority = ie->value.choice.PagingPriority; + const char *s = text_info_at(*out->paging_priority, paging_prio_text, sizeofArray(paging_prio_text)); + NGAP_DEBUG("Decoded Paging Priority: %s", s); + } + + // UERadioCapabilityForPaging (O) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_UERadioCapabilityForPaging, false); + if (ie != NULL) { + const NGAP_UERadioCapabilityForPaging_t *ue_radio_cap = &ie->value.choice.UERadioCapabilityForPaging; + const OCTET_STRING_t *nr_field = ue_radio_cap->uERadioCapabilityForPagingOfNR; + const OCTET_STRING_t *eutra_field = ue_radio_cap->uERadioCapabilityForPagingOfEUTRA; + if (nr_field != NULL) { + out->ue_radio_capability = malloc_or_fail(sizeof(byte_array_t)); + *out->ue_radio_capability = create_byte_array(nr_field->size, nr_field->buf); + NGAP_DEBUG("Decoded UERadioCapabilityForPaging (NR): %zu bytes\n", nr_field->size); + } else if (eutra_field != NULL) { + out->ue_radio_capability = malloc_or_fail(sizeof(byte_array_t)); + *out->ue_radio_capability = create_byte_array(eutra_field->size, eutra_field->buf); + NGAP_DEBUG("Decoded UERadioCapabilityForPaging (EUTRA): %zu bytes\n", eutra_field->size); + } else { + NGAP_WARN("UERadioCapabilityForPaging present but both NR and EUTRA fields are NULL\n"); + } + } + + // PagingOrigin (O) + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_PagingOrigin, false); + if (ie != NULL) { + // PagingOrigin shall be transferred to UE according to TS 38.331 and TS 36.331 + out->origin = calloc_or_fail(1, sizeof(*out->origin)); + *out->origin = ie->value.choice.PagingOrigin; + } + + // AssistanceDataForPaging (O) - contains PagingAttemptInformation + NGAP_FIND_PROTOCOLIE_BY_ID(NGAP_PagingIEs_t, ie, container, NGAP_ProtocolIE_ID_id_AssistanceDataForPaging, false); + if (ie != NULL) { + const NGAP_AssistanceDataForPaging_t *assistance_data = &ie->value.choice.AssistanceDataForPaging; + + // Extract PagingAttemptInformation if present (TS 38.413 §9.3.1.72) + if (assistance_data->pagingAttemptInformation != NULL) { + const NGAP_PagingAttemptInformation_t *paging_attempt_info = assistance_data->pagingAttemptInformation; + out->paging_attempt_info = calloc_or_fail(1, sizeof(ngap_paging_attempt_info_t)); + + // Paging Attempt Count (mandatory) + uint8_t attempt_count = paging_attempt_info->pagingAttemptCount; + DevAssert(attempt_count >= NGAP_PAGING_ATTEMPT_MIN && attempt_count <= NGAP_PAGING_ATTEMPT_MAX); + out->paging_attempt_info->paging_attempt_count = attempt_count; + NGAP_DEBUG("Decoded Paging Attempt Count: %u\n", out->paging_attempt_info->paging_attempt_count); + + // Intended Number of Paging Attempts (mandatory) + uint8_t intended_attempts = paging_attempt_info->intendedNumberOfPagingAttempts; + DevAssert(intended_attempts >= NGAP_PAGING_ATTEMPT_MIN && intended_attempts <= NGAP_PAGING_ATTEMPT_MAX); + out->paging_attempt_info->intended_paging_attempts = intended_attempts; + NGAP_DEBUG("Decoded Intended Number of Paging Attempts: %u\n", out->paging_attempt_info->intended_paging_attempts); + } + } + + NGAP_DEBUG("Decoded Paging: m_tmsi=0x%08x, TAI=%d", s_tmsi->m_tmsi, out->n_tai); + + return true; +} + +/** @brief Check equality of two NGAP paging indication structures + * @param a First structure + * @param b Second structure + * @return true if equal, false otherwise */ +bool eq_ng_paging(const ngap_paging_ind_t *a, const ngap_paging_ind_t *b) +{ + if (a == NULL || b == NULL) { + return a == b; + } + + // UE Paging Identity + const fiveg_s_tmsi_t *a_tmsi = &a->ue_paging_identity.s_tmsi; + const fiveg_s_tmsi_t *b_tmsi = &b->ue_paging_identity.s_tmsi; + _EQ_CHECK_INT(a_tmsi->amf_set_id, b_tmsi->amf_set_id); + _EQ_CHECK_INT(a_tmsi->amf_pointer, b_tmsi->amf_pointer); + _EQ_CHECK_UINT32(a_tmsi->m_tmsi, b_tmsi->m_tmsi); + + // TAI List + _EQ_CHECK_INT(a->n_tai, b->n_tai); + for (int i = 0; i < a->n_tai; i++) { + if (!eq_ngap_plmn(&a->tai_list[i].plmn, &b->tai_list[i].plmn)) { + return false; + } + _EQ_CHECK_INT(a->tai_list[i].tac, b->tai_list[i].tac); + } + + // Paging DRX (optional) + _EQ_CHECK_OPTIONAL_IE(a, b, paging_drx, _EQ_CHECK_INT); + + // Paging Priority (optional) + _EQ_CHECK_OPTIONAL_IE(a, b, paging_priority, _EQ_CHECK_INT); + + // UERadioCapabilityForPaging (optional) + _EQ_CHECK_OPTIONAL_PTR(a, b, ue_radio_capability); + if (a->ue_radio_capability != NULL && b->ue_radio_capability != NULL) { + if (!eq_byte_array(a->ue_radio_capability, b->ue_radio_capability)) { + NGAP_ERROR("Equality condition 'eq_byte_array' failed: byte array contents differ\n"); + return false; + } + } + + // PagingOrigin (optional) + _EQ_CHECK_OPTIONAL_IE(a, b, origin, _EQ_CHECK_INT); + + // Assistance Data for Paging (optional) + _EQ_CHECK_OPTIONAL_PTR(a, b, paging_attempt_info); + if (a->paging_attempt_info != NULL && b->paging_attempt_info != NULL) { + _EQ_CHECK_INT(a->paging_attempt_info->paging_attempt_count, b->paging_attempt_info->paging_attempt_count); + _EQ_CHECK_INT(a->paging_attempt_info->intended_paging_attempts, b->paging_attempt_info->intended_paging_attempts); + } + + return true; +} diff --git a/openair3/NGAP/ngap_gNB_paging.h b/openair3/NGAP/ngap_gNB_paging.h new file mode 100644 index 0000000000..3a07ce4c03 --- /dev/null +++ b/openair3/NGAP/ngap_gNB_paging.h @@ -0,0 +1,50 @@ +/* + * 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 Software Alliance: + * contact@openairinterface.org + */ + +#ifndef NGAP_GNB_PAGING_H_ +#define NGAP_GNB_PAGING_H_ + +#include +#include +#include "ngap_messages_types.h" + +/** @brief Encode NGAP Paging message (9.2.4.1 of 3GPP TS 38.413) + * @param msg Pointer to paging indication structure to encode + * @return Pointer to allocated NGAP_NGAP_PDU_t, or NULL on failure */ +NGAP_NGAP_PDU_t *encode_ng_paging(const ngap_paging_ind_t *msg); + +/** @brief Decode NGAP Paging message (9.2.4.1 of 3GPP TS 38.413) + * @param out Pointer to output structure to fill + * @param pdu Pointer to NGAP PDU containing the Paging message + * @return true on success, false on failure */ +bool decode_ng_paging(ngap_paging_ind_t *out, const NGAP_NGAP_PDU_t *pdu); + +/** @brief Free memory allocated for NGAP paging indication + * @param msg Pointer to paging indication structure to free */ +void free_ng_paging(ngap_paging_ind_t *msg); + +/** @brief Check equality of two NGAP paging indication structures + * @param a First structure + * @param b Second structure + * @return true if equal, false otherwise */ +bool eq_ng_paging(const ngap_paging_ind_t *a, const ngap_paging_ind_t *b); + +#endif /* NGAP_GNB_PAGING_H_ */ diff --git a/openair3/NGAP/ngap_msg_includes.h b/openair3/NGAP/ngap_msg_includes.h index 485c9f26ac..911461e47d 100644 --- a/openair3/NGAP/ngap_msg_includes.h +++ b/openair3/NGAP/ngap_msg_includes.h @@ -41,6 +41,12 @@ #include "NGAP_GTPTunnel.h" #include "NGAP_InitiatingMessage.h" #include "NGAP_NGAP-PDU.h" +#include "NGAP_Paging.h" +#include "NGAP_AssistanceDataForPaging.h" +#include "NGAP_PagingAttemptInformation.h" +#include "NGAP_FiveG-S-TMSI.h" +#include "NGAP_TAI.h" +#include "NGAP_UEPagingIdentity.h" #include "NGAP_PDUSessionResourceFailedToModifyItemModRes.h" #include "NGAP_PDUSessionResourceFailedToSetupItemCxtRes.h" #include "NGAP_PDUSessionResourceFailedToSetupItemSURes.h" diff --git a/openair3/NGAP/ngap_utils.h b/openair3/NGAP/ngap_utils.h index 34d288fb27..60ee9fbdfd 100644 --- a/openair3/NGAP/ngap_utils.h +++ b/openair3/NGAP/ngap_utils.h @@ -27,6 +27,7 @@ #include #include "common/utils/LOG/log.h" #include "assertions.h" +#include "common/utils/eq_check.h" #define NGAP_UE_ID_FMT "0x%06" PRIX32 diff --git a/openair3/NGAP/tests/CMakeLists.txt b/openair3/NGAP/tests/CMakeLists.txt new file mode 100644 index 0000000000..65cee63f8c --- /dev/null +++ b/openair3/NGAP/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(ngap_lib_test ngap_lib_test.c) +add_dependencies(tests ngap_lib_test) +add_test(NAME ngap_lib_test COMMAND ngap_lib_test) +target_link_libraries(ngap_lib_test PRIVATE ngap asn1_ngap asn1_lte_rrc_hdrs UTIL ds) +target_include_directories(ngap_lib_test PRIVATE ${CMAKE_SOURCE_DIR}) + diff --git a/openair3/NGAP/tests/ngap_lib_test.c b/openair3/NGAP/tests/ngap_lib_test.c new file mode 100644 index 0000000000..72a8667d94 --- /dev/null +++ b/openair3/NGAP/tests/ngap_lib_test.c @@ -0,0 +1,133 @@ +/* + * 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 Software Alliance: + * contact@openairinterface.org + */ + +#include // abort +#include // uint*_t +#include // printf +#include // bool + +#include "common/utils/assertions.h" // AssertFatal, DevAssert +#include "common/utils/utils.h" // malloc_or_fail, calloc_or_fail +#include "common/utils/ds/byte_array.h" // create_byte_array +#include "conversions.h" // TBCD_TO_MCC_MNC, OCTET_STRING_TO_INT* +#include "ngap_common.h" // NGAP_ERROR, NGAP_DEBUG, ngap_messages_types.h +#include "ngap_gNB_paging.h" // encode_ng_paging, decode_ng_paging, free_ng_paging +#include "ngap_msg_includes.h" // NGAP_NGAP_PDU_t, NGAP_Paging_t, etc. +#include "oai_asn1.h" // ASN_STRUCT_FREE, asn_DEF_NGAP_NGAP_PDU +#include "config/config_load_configmodule.h" // configmodule_interface_t +#include "common/utils/LOG/log.h" // log_init + +void exit_function(const char *file, const char *function, const int line, const char *s, const int assert) +{ + printf("detected error at %s:%d:%s: %s\n", file, line, function, s); + abort(); +} + +static NGAP_NGAP_PDU_t *ngap_encode_decode(const NGAP_NGAP_PDU_t *enc_pdu) +{ + DevAssert(enc_pdu != NULL); + char errbuf[1024]; + size_t errlen = sizeof(errbuf); + int ret = asn_check_constraints(&asn_DEF_NGAP_NGAP_PDU, enc_pdu, errbuf, &errlen); + AssertFatal(ret == 0, "asn_check_constraints() failed: %s\n", errbuf); + + uint8_t msgbuf[16384]; + asn_enc_rval_t enc = aper_encode_to_buffer(&asn_DEF_NGAP_NGAP_PDU, NULL, enc_pdu, msgbuf, sizeof(msgbuf)); + AssertFatal(enc.encoded > 0, "aper_encode_to_buffer() failed\n"); + + NGAP_NGAP_PDU_t *dec_pdu = NULL; + asn_codec_ctx_t st = {.max_stack_size = 100 * 1000}; + asn_dec_rval_t dec = aper_decode(&st, &asn_DEF_NGAP_NGAP_PDU, (void **)&dec_pdu, msgbuf, enc.encoded, 0, 0); + AssertFatal(dec.code == RC_OK, "aper_decode() failed\n"); + + return dec_pdu; +} + +static void ngap_msg_free(NGAP_NGAP_PDU_t *pdu) +{ + ASN_STRUCT_FREE(asn_DEF_NGAP_NGAP_PDU, pdu); +} + +/** @brief Test NGAP Paging encoding/decoding roundtrip */ +static void test_ngap_paging_encode_decode(void) +{ + // Create test paging indication + ngap_paging_ind_t orig = {0}; + orig.ue_paging_identity.s_tmsi.amf_set_id = 123; // Valid range: 0-1023 (10 bits) + orig.ue_paging_identity.s_tmsi.amf_pointer = 45; // Valid range: 0-63 (6 bits) + orig.ue_paging_identity.s_tmsi.m_tmsi = 0x12345678; + orig.n_tai = 2; + orig.tai_list[0].plmn.mcc = 208; + orig.tai_list[0].plmn.mnc = 95; + orig.tai_list[0].plmn.mnc_digit_length = 2; + orig.tai_list[0].tac = 12345; + orig.tai_list[1].plmn.mcc = 001; + orig.tai_list[1].plmn.mnc = 01; + orig.tai_list[1].plmn.mnc_digit_length = 2; + orig.tai_list[1].tac = 23456; + orig.paging_drx = malloc_or_fail(sizeof(ngap_paging_drx_t)); + *orig.paging_drx = NGAP_PAGING_DRX_128; + orig.paging_priority = malloc_or_fail(sizeof(ngap_paging_priority_t)); + *orig.paging_priority = NGAP_PAGING_PRIO_LEVEL2; + uint8_t ue_cap_data[] = {0x01, 0x02, 0x03, 0x04}; + orig.ue_radio_capability = malloc_or_fail(sizeof(byte_array_t)); + *orig.ue_radio_capability = create_byte_array(sizeof(ue_cap_data), ue_cap_data); + orig.origin = calloc_or_fail(1, sizeof(*orig.origin)); + *orig.origin = NGAP_PAGING_ORIGIN_NON_3GPP; + + // Encode + NGAP_NGAP_PDU_t *enc_pdu = encode_ng_paging(&orig); + AssertFatal(enc_pdu != NULL, "encode_ng_paging(): failed to encode message\n"); + LOG_A(NGAP, "encoded: m_tmsi=0x%x, n_tai=%d\n", orig.ue_paging_identity.s_tmsi.m_tmsi, orig.n_tai); + + // Encode/decode roundtrip + NGAP_NGAP_PDU_t *dec_pdu = ngap_encode_decode(enc_pdu); + ngap_msg_free(enc_pdu); + + // Decode + ngap_paging_ind_t decoded = {0}; + bool decode_ret = decode_ng_paging(&decoded, dec_pdu); + AssertFatal(decode_ret, "decode_ng_paging(): could not decode message\n"); + ngap_msg_free(dec_pdu); + LOG_A(NGAP, "decoded: m_tmsi=0x%x, n_tai=%d\n", decoded.ue_paging_identity.s_tmsi.m_tmsi, decoded.n_tai); + + // Verify decoded values using equality function + bool eq_ret = eq_ng_paging(&orig, &decoded); + AssertFatal(eq_ret, "eq_ng_paging(): decoded message doesn't match original\n"); + LOG_A(NGAP, "eq_ng_paging(): decoded message matches original\n"); + + free_ng_paging(&decoded); + free_ng_paging(&orig); +} + +// Stub for uniqCfg required by logging/config system +configmodule_interface_t *uniqCfg = NULL; + +int main(void) +{ + // Initialize logging system + logInit(); + set_log(NGAP, OAILOG_INFO); + + test_ngap_paging_encode_decode(); + LOG_A(NGAP, "All NGAP paging tests passed!\n"); + return 0; +}