From 32310cced30453a3f26a0b2b90e5c21e745a4d35 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 6 Feb 2025 18:28:01 +0100 Subject: [PATCH 01/10] Bugfix: free E1 CU-UP Setup Req on CU-UP disconnection --- openair2/RRC/NR/rrc_gNB_cuup.c | 1 + 1 file changed, 1 insertion(+) diff --git a/openair2/RRC/NR/rrc_gNB_cuup.c b/openair2/RRC/NR/rrc_gNB_cuup.c index b2ef792ac5..689670fa85 100644 --- a/openair2/RRC/NR/rrc_gNB_cuup.c +++ b/openair2/RRC/NR/rrc_gNB_cuup.c @@ -241,6 +241,7 @@ void rrc_gNB_process_e1_lost_connection(gNB_RRC_INST *rrc, e1ap_lost_connection_ if (cuup->setup_req != NULL) { e1ap_setup_req_t *req = cuup->setup_req; LOG_I(NR_RRC, "releasing CU-UP %s on assoc_id %d\n", req->gNB_cu_up_name, assoc_id); + free_e1ap_cuup_setup_request(cuup->setup_req); free(cuup->setup_req); } nr_rrc_cuup_container_t *removed = RB_REMOVE(rrc_cuup_tree, &rrc->cuups, cuup); From b9acf4a8e32a3f1a036f90e2a5f5a2a943e747ae Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 5 Feb 2025 16:24:50 +0100 Subject: [PATCH 02/10] Refactor E1 UE removal in separate function Refactor into remove_ue_e1(), to be used in the next commit, independent of e1_bearer_release_cmd(). --- openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index 380ae1f60a..dc9b5b52ee 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -300,24 +300,28 @@ void e1_bearer_context_modif(const e1ap_bearer_mod_req_t *req) get_e1_if()->bearer_modif_response(&modif); } -void e1_bearer_release_cmd(const e1ap_bearer_release_cmd_t *cmd) +static void remove_ue_e1(const uint32_t ue_id) { bool need_ue_id_mgmt = e1_used(); instance_t n3inst = get_n3_gtp_instance(); instance_t f1inst = get_f1_gtp_instance(); - LOG_I(E1AP, "releasing UE %d\n", cmd->gNB_cu_up_ue_id); - - newGtpuDeleteAllTunnels(n3inst, cmd->gNB_cu_up_ue_id); + newGtpuDeleteAllTunnels(n3inst, ue_id); if (f1inst >= 0) // is there F1-U? - newGtpuDeleteAllTunnels(f1inst, cmd->gNB_cu_up_ue_id); + newGtpuDeleteAllTunnels(f1inst, ue_id); if (need_ue_id_mgmt) { // see issue #706: in monolithic, gNB will free PDCP of UE - nr_pdcp_remove_UE(cmd->gNB_cu_up_ue_id); - cu_remove_f1_ue_data(cmd->gNB_cu_up_ue_id); + nr_pdcp_remove_UE(ue_id); + cu_remove_f1_ue_data(ue_id); } - nr_sdap_delete_ue_entities(cmd->gNB_cu_up_ue_id); + nr_sdap_delete_ue_entities(ue_id); +} + +void e1_bearer_release_cmd(const e1ap_bearer_release_cmd_t *cmd) +{ + LOG_I(E1AP, "releasing UE %d\n", cmd->gNB_cu_up_ue_id); + remove_ue_e1(cmd->gNB_cu_up_ue_id); e1ap_bearer_release_cplt_t cplt = { .gNB_cu_cp_ue_id = cmd->gNB_cu_cp_ue_id, From deba3ae066df7076f8918b491faed84bedaa4e23 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 5 Feb 2025 16:44:31 +0100 Subject: [PATCH 03/10] Reset CU-UP E1 UE contexts after E1 Setup Response 38.463 sec. 8.2.3 says > This procedure also re-initialises the E1AP UE-related contexts (if > any) and erases all related signalling connections in the two nodes like > a Reset procedure would do. Hence, delete all contexts after reception of E1 Setup Response. This minimizes a risk of receiving an E1 bearer setup req for an existing UE, which currently leads to an assert [we trust the CU-CP does not send the same UE ID twice, as we only "mirror" the existing UE ID]. --- openair2/E1AP/e1ap.c | 2 ++ openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c | 12 ++++++++++++ openair2/LAYER2/nr_pdcp/cucp_cuup_handler.h | 1 + 3 files changed, 15 insertions(+) diff --git a/openair2/E1AP/e1ap.c b/openair2/E1AP/e1ap.c index 95046cb652..e085a9e9b9 100644 --- a/openair2/E1AP/e1ap.c +++ b/openair2/E1AP/e1ap.c @@ -219,6 +219,8 @@ int e1apCUUP_handle_SETUP_RESPONSE(sctp_assoc_t assoc_id, e1ap_upcp_inst_t *inst return -1; } free_e1ap_cuup_setup_response(&out); + + e1_reset(); // reset all UE contexts, if any: see 38.463 sec 8.2.3 return 0; } diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c index dc9b5b52ee..dd281fb77d 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.c @@ -330,3 +330,15 @@ void e1_bearer_release_cmd(const e1ap_bearer_release_cmd_t *cmd) get_e1_if()->bearer_release_complete(&cplt); } + +void e1_reset(void) +{ + /* we get the list of all UEs from the PDCP, which maintains a list */ + ue_id_t ue_ids[MAX_MOBILES_PER_GNB]; + int num = nr_pdcp_get_num_ues(ue_ids, MAX_MOBILES_PER_GNB); + for (uint32_t i = 0; i < num; ++i) { + ue_id_t ue_id = ue_ids[i]; + LOG_W(E1AP, "releasing UE %ld\n", ue_id); + remove_ue_e1(ue_id); + } +} diff --git a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.h b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.h index 7290043371..521a33ea99 100644 --- a/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.h +++ b/openair2/LAYER2/nr_pdcp/cucp_cuup_handler.h @@ -31,5 +31,6 @@ struct e1ap_bearer_release_cmd_s; void e1_bearer_context_setup(const struct e1ap_bearer_setup_req_s *req); void e1_bearer_context_modif(const struct e1ap_bearer_setup_req_s *req); void e1_bearer_release_cmd(const struct e1ap_bearer_release_cmd_s *cmd); +void e1_reset(void); #endif /* CUCP_CUUP_HANDLER_H */ From 27a418572d3f8f73bda5ed78f9900d5b1b756a31 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 10 Feb 2025 08:59:47 +0100 Subject: [PATCH 04/10] Remove unnecessary includes f1ap_lib_common should help with common message enc/dec inside lib/, and not be used directly. --- openair2/F1AP/f1ap_cu_interface_management.c | 1 - openair2/F1AP/f1ap_du_interface_management.c | 1 - 2 files changed, 2 deletions(-) diff --git a/openair2/F1AP/f1ap_cu_interface_management.c b/openair2/F1AP/f1ap_cu_interface_management.c index 9e25aeb63e..b81d81c6ba 100644 --- a/openair2/F1AP/f1ap_cu_interface_management.c +++ b/openair2/F1AP/f1ap_cu_interface_management.c @@ -36,7 +36,6 @@ #include "f1ap_cu_interface_management.h" #include "f1ap_default_values.h" #include "lib/f1ap_interface_management.h" -#include "lib/f1ap_lib_common.h" int CU_handle_RESET_ACKNOWLEDGE(instance_t instance, sctp_assoc_t assoc_id, uint32_t stream, F1AP_F1AP_PDU_t *pdu) { diff --git a/openair2/F1AP/f1ap_du_interface_management.c b/openair2/F1AP/f1ap_du_interface_management.c index f12b104263..0d0c9c2946 100644 --- a/openair2/F1AP/f1ap_du_interface_management.c +++ b/openair2/F1AP/f1ap_du_interface_management.c @@ -35,7 +35,6 @@ #include "f1ap_itti_messaging.h" #include "f1ap_du_interface_management.h" #include "lib/f1ap_interface_management.h" -#include "lib/f1ap_lib_common.h" #include "openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.h" #include "assertions.h" From 9157e22c7c7072f45c5d9fa8cf5ba190c17cd9ad Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 10 Feb 2025 11:37:06 +0100 Subject: [PATCH 05/10] Add F1 Reset enc/dec/cp/eq/free library and test --- openair2/COMMON/f1ap_messages_types.h | 11 +- openair2/F1AP/lib/f1ap_interface_management.c | 220 ++++++++++++++++++ openair2/F1AP/lib/f1ap_interface_management.h | 7 + openair2/F1AP/lib/f1ap_lib_common.c | 55 +++++ openair2/F1AP/lib/f1ap_lib_common.h | 6 + openair2/F1AP/lib/f1ap_lib_includes.h | 1 + openair2/F1AP/tests/f1ap_lib_test.c | 66 ++++++ 7 files changed, 362 insertions(+), 4 deletions(-) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index 36c3ad4f76..e72741f6ed 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -563,15 +563,18 @@ typedef enum F1AP_ResetType_e { F1AP_RESET_PART_OF_F1_INTERFACE } f1ap_ResetType_t; +typedef struct f1ap_ue_to_reset_t { + uint32_t *gNB_CU_ue_id; + uint32_t *gNB_DU_ue_id; +} f1ap_ue_to_reset_t; + typedef struct f1ap_reset_t { uint64_t transaction_id; f1ap_Cause_t cause; long cause_value; f1ap_ResetType_t reset_type; - struct { - uint32_t gNB_CU_ue_id; - uint32_t gNB_DU_ue_id; - } ue_to_reset[F1AP_MAX_NO_OF_INDIVIDUAL_CONNECTIONS_TO_RESET]; + int num_ue_to_reset; + f1ap_ue_to_reset_t *ue_to_reset; // array of num_ue_to_reset elements } f1ap_reset_t; typedef struct f1ap_reset_ack_t { diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index 182f40952d..ce7ece3c11 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -32,6 +32,226 @@ #include "f1ap_messages_types.h" #include "f1ap_lib_extern.h" +F1AP_UE_associatedLogicalF1_ConnectionItem_t encode_f1ap_ue_to_reset(const f1ap_ue_to_reset_t *to_reset) +{ + F1AP_UE_associatedLogicalF1_ConnectionItem_t conn_it = {0}; + + if (to_reset->gNB_CU_ue_id) + asn1cCallocOne(conn_it.gNB_CU_UE_F1AP_ID, *to_reset->gNB_CU_ue_id); + if (to_reset->gNB_DU_ue_id) + asn1cCallocOne(conn_it.gNB_DU_UE_F1AP_ID, *to_reset->gNB_DU_ue_id); + + return conn_it; +} + +f1ap_ue_to_reset_t decode_f1ap_ue_to_reset(const F1AP_UE_associatedLogicalF1_ConnectionItem_t *conn_it) +{ + f1ap_ue_to_reset_t to_reset = {0}; + if (conn_it->gNB_CU_UE_F1AP_ID) { + to_reset.gNB_CU_ue_id = malloc_or_fail(sizeof(*to_reset.gNB_CU_ue_id)); + *to_reset.gNB_CU_ue_id = *conn_it->gNB_CU_UE_F1AP_ID; + } + if (conn_it->gNB_DU_UE_F1AP_ID) { + to_reset.gNB_DU_ue_id = malloc_or_fail(sizeof(*to_reset.gNB_DU_ue_id)); + *to_reset.gNB_DU_ue_id = *conn_it->gNB_DU_UE_F1AP_ID; + } + return to_reset; +} + +void free_f1ap_ue_to_reset(const f1ap_ue_to_reset_t *to_reset) +{ + free(to_reset->gNB_CU_ue_id); + free(to_reset->gNB_DU_ue_id); +} + +bool eq_f1ap_ue_to_reset(const f1ap_ue_to_reset_t *a, const f1ap_ue_to_reset_t *b) +{ + if ((!a->gNB_CU_ue_id) ^ (!b->gNB_CU_ue_id)) + return false; + if (a->gNB_CU_ue_id) + _F1_EQ_CHECK_INT(*a->gNB_CU_ue_id, *b->gNB_CU_ue_id); + if ((!a->gNB_DU_ue_id) ^ (!b->gNB_DU_ue_id)) + return false; + if (a->gNB_DU_ue_id) + _F1_EQ_CHECK_INT(*a->gNB_DU_ue_id, *b->gNB_DU_ue_id); + return true; +} + +f1ap_ue_to_reset_t cp_f1ap_ue_to_reset(const f1ap_ue_to_reset_t *orig) +{ + f1ap_ue_to_reset_t cp = {0}; + if (orig->gNB_CU_ue_id) { + cp.gNB_CU_ue_id = malloc_or_fail(sizeof(*cp.gNB_CU_ue_id)); + *cp.gNB_CU_ue_id = *orig->gNB_CU_ue_id; + } + if (orig->gNB_DU_ue_id) { + cp.gNB_DU_ue_id = malloc_or_fail(sizeof(*cp.gNB_DU_ue_id)); + *cp.gNB_DU_ue_id = *orig->gNB_DU_ue_id; + } + return cp; +} + +/* @brief encode F1 Reset (9.2.1.1 in TS 38.473) */ +F1AP_F1AP_PDU_t *encode_f1ap_reset(const f1ap_reset_t *msg) +{ + F1AP_F1AP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); + /* Create Message Type */ + pdu->present = F1AP_F1AP_PDU_PR_initiatingMessage; + asn1cCalloc(pdu->choice.initiatingMessage, initMsg); + initMsg->procedureCode = F1AP_ProcedureCode_id_Reset; + initMsg->criticality = F1AP_Criticality_reject; + initMsg->value.present = F1AP_InitiatingMessage__value_PR_Reset; + F1AP_Reset_t *reset = &initMsg->value.choice.Reset; + + /* (M) Transaction ID */ + asn1cSequenceAdd(reset->protocolIEs.list, F1AP_ResetIEs_t, ieC1); + ieC1->id = F1AP_ProtocolIE_ID_id_TransactionID; + ieC1->criticality = F1AP_Criticality_reject; + ieC1->value.present = F1AP_ResetIEs__value_PR_TransactionID; + ieC1->value.choice.TransactionID = msg->transaction_id; + + /* (M) Cause */ + asn1cSequenceAdd(reset->protocolIEs.list, F1AP_ResetIEs_t, ieC2); + ieC2->id = F1AP_ProtocolIE_ID_id_Cause; + ieC2->criticality = F1AP_Criticality_ignore; + ieC2->value.present = F1AP_ResetIEs__value_PR_Cause; + ieC2->value.choice.Cause = encode_f1ap_cause(msg->cause, msg->cause_value); + + /* (M) Reset type */ + asn1cSequenceAdd(reset->protocolIEs.list, F1AP_ResetIEs_t, ieC3); + ieC3->id = F1AP_ProtocolIE_ID_id_ResetType; + ieC3->criticality = F1AP_Criticality_reject; + ieC3->value.present = F1AP_ResetIEs__value_PR_ResetType; + if (msg->reset_type == F1AP_RESET_ALL) { + ieC3->value.choice.ResetType.present = F1AP_ResetType_PR_f1_Interface; + ieC3->value.choice.ResetType.choice.f1_Interface = F1AP_ResetAll_reset_all; + AssertFatal(msg->num_ue_to_reset == 0, "cannot have F1AP_RESET_ALL and %d UEs to reset\n", msg->num_ue_to_reset); + } else if (msg->reset_type == F1AP_RESET_PART_OF_F1_INTERFACE) { + F1AP_UE_associatedLogicalF1_ConnectionListRes_t *con_list = calloc_or_fail(1, sizeof(*con_list)); + ieC3->value.choice.ResetType.present = F1AP_ResetType_PR_partOfF1_Interface; + ieC3->value.choice.ResetType.choice.partOfF1_Interface = con_list; + AssertFatal(msg->num_ue_to_reset > 0, "at least one UE to reset required\n"); + for (int i = 0; i < msg->num_ue_to_reset; ++i) { + asn1cSequenceAdd(con_list->list, F1AP_UE_associatedLogicalF1_ConnectionItemRes_t, conn_it_res); + conn_it_res->id = F1AP_ProtocolIE_ID_id_UE_associatedLogicalF1_ConnectionItem; + conn_it_res->criticality = F1AP_Criticality_reject; + conn_it_res->value.present = F1AP_UE_associatedLogicalF1_ConnectionItemRes__value_PR_UE_associatedLogicalF1_ConnectionItem; + conn_it_res->value.choice.UE_associatedLogicalF1_ConnectionItem = encode_f1ap_ue_to_reset(&msg->ue_to_reset[i]); + } + } else { + AssertFatal(false, "illegal reset_type %d\n", msg->reset_type); + } + + return pdu; +} + +/* @brief decode F1 Reset (9.2.1.1 in TS 38.473) */ +bool decode_f1ap_reset(const F1AP_F1AP_PDU_t *pdu, f1ap_reset_t *out) +{ + _F1_EQ_CHECK_INT(pdu->present, F1AP_F1AP_PDU_PR_initiatingMessage); + AssertError(pdu->choice.initiatingMessage != NULL, return false, "pdu->choice.initiatingMessage is NULL"); + _F1_EQ_CHECK_LONG(pdu->choice.initiatingMessage->procedureCode, F1AP_ProcedureCode_id_Reset); + _F1_EQ_CHECK_INT(pdu->choice.initiatingMessage->value.present, F1AP_InitiatingMessage__value_PR_Reset); + + /* Check presence of mandatory IEs */ + F1AP_Reset_t *in = &pdu->choice.initiatingMessage->value.choice.Reset; + F1AP_ResetIEs_t *ie; + F1AP_LIB_FIND_IE(F1AP_ResetIEs_t, ie, in, F1AP_ProtocolIE_ID_id_TransactionID, true); + F1AP_LIB_FIND_IE(F1AP_ResetIEs_t, ie, in, F1AP_ProtocolIE_ID_id_Cause, true); + F1AP_LIB_FIND_IE(F1AP_ResetIEs_t, ie, in, F1AP_ProtocolIE_ID_id_ResetType, true); + + /* Loop over all IEs */ + for (int i = 0; i < in->protocolIEs.list.count; i++) { + AssertError(in->protocolIEs.list.array[i] != NULL, return false, "in->protocolIEs.list.array[i] is NULL"); + ie = in->protocolIEs.list.array[i]; + switch (ie->id) { + case F1AP_ProtocolIE_ID_id_TransactionID: + // (M) Transaction ID + _F1_EQ_CHECK_INT(ie->value.present, F1AP_ResetIEs__value_PR_TransactionID); + out->transaction_id = ie->value.choice.TransactionID; + break; + case F1AP_ProtocolIE_ID_id_Cause: + // (M) Cause + _F1_EQ_CHECK_INT(ie->value.present, F1AP_ResetIEs__value_PR_Cause); + if (!decode_f1ap_cause(ie->value.choice.Cause, &out->cause, &out->cause_value)) { + PRINT_ERROR("could not decode F1AP Cause\n"); + return false; + } + break; + case F1AP_ProtocolIE_ID_id_ResetType: + // (M) Reset type + _F1_EQ_CHECK_INT(ie->value.present, F1AP_ResetIEs__value_PR_ResetType); + if (ie->value.choice.ResetType.present == F1AP_ResetType_PR_f1_Interface) { + out->reset_type = F1AP_RESET_ALL; + } else if (ie->value.choice.ResetType.present == F1AP_ResetType_PR_partOfF1_Interface) { + out->reset_type = F1AP_RESET_PART_OF_F1_INTERFACE; + const F1AP_UE_associatedLogicalF1_ConnectionListRes_t *con_list = ie->value.choice.ResetType.choice.partOfF1_Interface; + AssertError(con_list->list.count > 0, return false, "no UEs for partially reset F1 interface\n"); + out->num_ue_to_reset = con_list->list.count; + out->ue_to_reset = calloc_or_fail(out->num_ue_to_reset, sizeof(*out->ue_to_reset)); + for (int i = 0; i < out->num_ue_to_reset; ++i) { + const F1AP_UE_associatedLogicalF1_ConnectionItemRes_t *it_res = + (const F1AP_UE_associatedLogicalF1_ConnectionItemRes_t *)con_list->list.array[i]; + _F1_EQ_CHECK_LONG(it_res->id, F1AP_ProtocolIE_ID_id_UE_associatedLogicalF1_ConnectionItem); + _F1_EQ_CHECK_INT(it_res->value.present, + F1AP_UE_associatedLogicalF1_ConnectionItemRes__value_PR_UE_associatedLogicalF1_ConnectionItem); + out->ue_to_reset[i] = decode_f1ap_ue_to_reset(&it_res->value.choice.UE_associatedLogicalF1_ConnectionItem); + } + } else { + PRINT_ERROR("unrecognized Reset type %d\n", ie->value.choice.ResetType.present); + return false; + } + } + } + return true; +} + +void free_f1ap_reset(f1ap_reset_t *msg) +{ + DevAssert(msg->reset_type == F1AP_RESET_ALL || msg->reset_type == F1AP_RESET_PART_OF_F1_INTERFACE); + if (msg->reset_type == F1AP_RESET_ALL) { + DevAssert(msg->ue_to_reset == NULL); + return; /* nothing to be freed in this case */ + } + for (int i = 0; i < msg->num_ue_to_reset; ++i) + free_f1ap_ue_to_reset(&msg->ue_to_reset[i]); + free(msg->ue_to_reset); +} + +bool eq_f1ap_reset(const f1ap_reset_t *a, const f1ap_reset_t *b) +{ + _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); + _F1_EQ_CHECK_INT(a->cause, b->cause); + _F1_EQ_CHECK_LONG(a->cause_value, b->cause_value); + _F1_EQ_CHECK_INT(a->reset_type, b->reset_type); + if (a->reset_type == F1AP_RESET_PART_OF_F1_INTERFACE) { + _F1_EQ_CHECK_INT(a->num_ue_to_reset, b->num_ue_to_reset); + for (int i = 0; i < a->num_ue_to_reset; ++i) + if (!eq_f1ap_ue_to_reset(&a->ue_to_reset[i], &b->ue_to_reset[i])) + return false; + } + return true; +} + +f1ap_reset_t cp_f1ap_reset(const f1ap_reset_t *orig) +{ + DevAssert(orig->reset_type == F1AP_RESET_ALL || orig->reset_type == F1AP_RESET_PART_OF_F1_INTERFACE); + f1ap_reset_t cp = { + .transaction_id = orig->transaction_id, + .cause = orig->cause, + .cause_value = orig->cause_value, + .reset_type = orig->reset_type, + }; + if (orig->reset_type == F1AP_RESET_PART_OF_F1_INTERFACE) { + DevAssert(orig->num_ue_to_reset > 0); + cp.num_ue_to_reset = orig->num_ue_to_reset; + cp.ue_to_reset = calloc_or_fail(cp.num_ue_to_reset, sizeof(*cp.ue_to_reset)); + for (int i = 0; i < cp.num_ue_to_reset; ++i) + cp.ue_to_reset[i] = cp_f1ap_ue_to_reset(&orig->ue_to_reset[i]); + } + return cp; +} + static const int nrb_lut[29] = {11, 18, 24, 25, 31, 32, 38, 51, 52, 65, 66, 78, 79, 93, 106, 107, 121, 132, 133, 135, 160, 162, 189, 216, 217, 245, 264, 270, 273}; diff --git a/openair2/F1AP/lib/f1ap_interface_management.h b/openair2/F1AP/lib/f1ap_interface_management.h index 6fd79b4007..b3769eabfe 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.h +++ b/openair2/F1AP/lib/f1ap_interface_management.h @@ -27,6 +27,13 @@ struct F1AP_F1AP_PDU; +/* F1 Reset */ +struct F1AP_F1AP_PDU *encode_f1ap_reset(const f1ap_reset_t *msg); +bool decode_f1ap_reset(const struct F1AP_F1AP_PDU *pdu, f1ap_reset_t *out); +void free_f1ap_reset(f1ap_reset_t *msg); +bool eq_f1ap_reset(const f1ap_reset_t *a, const f1ap_reset_t *b); +f1ap_reset_t cp_f1ap_reset(const f1ap_reset_t *orig); + struct F1AP_F1AP_PDU *encode_f1ap_setup_request(const f1ap_setup_req_t *msg); bool decode_f1ap_setup_request(const struct F1AP_F1AP_PDU *pdu, f1ap_setup_req_t *out); f1ap_setup_req_t cp_f1ap_setup_request(const f1ap_setup_req_t *msg); diff --git a/openair2/F1AP/lib/f1ap_lib_common.c b/openair2/F1AP/lib/f1ap_lib_common.c index a569146815..c49a87c57c 100644 --- a/openair2/F1AP/lib/f1ap_lib_common.c +++ b/openair2/F1AP/lib/f1ap_lib_common.c @@ -109,3 +109,58 @@ uint8_t *cp_octet_string(const OCTET_STRING_t *os, int *len) *len = os->size; return buf; } + +F1AP_Cause_t encode_f1ap_cause(f1ap_Cause_t cause, long cause_value) +{ + F1AP_Cause_t f1_cause = {0}; + switch (cause) { + case F1AP_CAUSE_RADIO_NETWORK: + f1_cause.present = F1AP_Cause_PR_radioNetwork; + f1_cause.choice.radioNetwork = cause_value; + break; + case F1AP_CAUSE_TRANSPORT: + f1_cause.present = F1AP_Cause_PR_transport; + f1_cause.choice.transport = cause_value; + break; + case F1AP_CAUSE_PROTOCOL: + f1_cause.present = F1AP_Cause_PR_protocol; + f1_cause.choice.protocol = cause_value; + break; + case F1AP_CAUSE_MISC: + f1_cause.present = F1AP_Cause_PR_misc; + f1_cause.choice.misc = cause_value; + break; + case F1AP_CAUSE_NOTHING: + default: + AssertFatal(false, "unknown cause value %d\n", cause); + break; + } + return f1_cause; +} + +bool decode_f1ap_cause(F1AP_Cause_t f1_cause, f1ap_Cause_t *cause, long *cause_value) +{ + switch (f1_cause.present) { + case F1AP_Cause_PR_radioNetwork: + *cause = F1AP_CAUSE_RADIO_NETWORK; + *cause_value = f1_cause.choice.radioNetwork; + break; + case F1AP_Cause_PR_transport: + *cause = F1AP_CAUSE_TRANSPORT; + *cause_value = f1_cause.choice.transport; + break; + case F1AP_Cause_PR_protocol: + *cause = F1AP_CAUSE_PROTOCOL; + *cause_value = f1_cause.choice.protocol; + break; + case F1AP_Cause_PR_misc: + *cause = F1AP_CAUSE_MISC; + *cause_value = f1_cause.choice.radioNetwork; + break; + case F1AP_Cause_PR_NOTHING: + default: + PRINT_ERROR("received illegal F1AP cause %d\n", f1_cause.present); + return false; + } + return true; +} diff --git a/openair2/F1AP/lib/f1ap_lib_common.h b/openair2/F1AP/lib/f1ap_lib_common.h index 2380a2da87..502200d9df 100644 --- a/openair2/F1AP/lib/f1ap_lib_common.h +++ b/openair2/F1AP/lib/f1ap_lib_common.h @@ -27,6 +27,9 @@ #include #include "openair3/UTILS/conversions.h" +#include "F1AP_Cause.h" +#include "f1ap_messages_types.h" + #ifdef ENABLE_TESTS #define PRINT_ERROR(...) fprintf(stderr, ##__VA_ARGS__) #else @@ -82,4 +85,7 @@ bool eq_f1ap_tx_bandwidth(const struct f1ap_transmission_bandwidth_t *a, const s struct OCTET_STRING; uint8_t *cp_octet_string(const struct OCTET_STRING *os, int *len); +F1AP_Cause_t encode_f1ap_cause(f1ap_Cause_t cause, long cause_value); +bool decode_f1ap_cause(F1AP_Cause_t f1_cause, f1ap_Cause_t *cause, long *cause_value); + #endif /* F1AP_LIB_COMMON_H_ */ diff --git a/openair2/F1AP/lib/f1ap_lib_includes.h b/openair2/F1AP/lib/f1ap_lib_includes.h index 6ff60a4897..267e7243e7 100644 --- a/openair2/F1AP/lib/f1ap_lib_includes.h +++ b/openair2/F1AP/lib/f1ap_lib_includes.h @@ -40,5 +40,6 @@ #include "F1AP_SuccessfulOutcome.h" #include "F1AP_SibtypetobeupdatedListItem.h" #include "F1AP_UnsuccessfulOutcome.h" +#include "F1AP_UE-associatedLogicalF1-ConnectionListRes.h" #endif /* F1AP_LIB_INCLUDES_H_ */ diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 5d153f1aea..2e516a5607 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -383,6 +383,70 @@ static void test_f1ap_setup_failure(void) AssertFatal(ret, "eq_f1ap_setup_failure(): copied message doesn't match\n"); } +static void _test_f1ap_reset_msg(const f1ap_reset_t *orig) +{ + F1AP_F1AP_PDU_t *f1enc = encode_f1ap_reset(orig); + F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); + f1ap_msg_free(f1enc); + + f1ap_reset_t decoded = {0}; + bool ret = decode_f1ap_reset(f1dec, &decoded); + AssertFatal(ret, "decode_f1ap_reset(): could not decode message\n"); + f1ap_msg_free(f1dec); + + ret = eq_f1ap_reset(orig, &decoded); + AssertFatal(ret, "eq_f1ap_reset(): decoded message doesn't match original\n"); + free_f1ap_reset(&decoded); + + f1ap_reset_t cp = cp_f1ap_reset(orig); + ret = eq_f1ap_reset(orig, &cp); + AssertFatal(ret, "eq_f1ap_reset(): copied message doesn't match original\n"); + free_f1ap_reset(&cp); + + printf("f1ap_reset successful\n"); +} + +/** + * @brief Test F1AP Reset message where the entire F1 interface is reset + */ +static void test_f1ap_reset_all(void) +{ + f1ap_reset_t orig = { + .transaction_id = 2, + .cause = F1AP_CAUSE_TRANSPORT, + .cause_value = 3, /* no type -> whatever */ + .reset_type = F1AP_RESET_ALL, + }; + _test_f1ap_reset_msg(&orig); + free_f1ap_reset(&orig); +} + +/** + * @brief Test F1AP Reset message where only some UEs are marked to reset + */ +static void test_f1ap_reset_part(void) +{ + f1ap_reset_t orig = { + .transaction_id = 3, + .cause = F1AP_CAUSE_MISC, + .cause_value = 3, /* no type -> whatever */ + .reset_type = F1AP_RESET_PART_OF_F1_INTERFACE, + .num_ue_to_reset = 4, + }; + orig.ue_to_reset = calloc_or_fail(orig.num_ue_to_reset, sizeof(*orig.ue_to_reset)); + orig.ue_to_reset[0].gNB_CU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[0].gNB_CU_ue_id)); + *orig.ue_to_reset[0].gNB_CU_ue_id = 10; + orig.ue_to_reset[1].gNB_DU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[1].gNB_DU_ue_id)); + *orig.ue_to_reset[1].gNB_DU_ue_id = 11; + orig.ue_to_reset[2].gNB_CU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[2].gNB_CU_ue_id)); + *orig.ue_to_reset[2].gNB_CU_ue_id = 12; + orig.ue_to_reset[2].gNB_DU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[2].gNB_DU_ue_id)); + *orig.ue_to_reset[2].gNB_DU_ue_id = 13; + // orig.ue_to_reset[3] intentionally empty (because it is allowed, both IDs are optional) + _test_f1ap_reset_msg(&orig); + free_f1ap_reset(&orig); +} + /** * @brief Test F1 gNB-DU Configuration Update */ @@ -605,6 +669,8 @@ int main() test_f1ap_setup_request(); test_f1ap_setup_response(); test_f1ap_setup_failure(); + test_f1ap_reset_all(); + test_f1ap_reset_part(); test_f1ap_du_configuration_update(); test_f1ap_cu_configuration_update(); test_f1ap_cu_configuration_update_acknowledge(); From 4c9ed9d7ecfc5ed55f427df28b824b0c50e85af5 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 10 Feb 2025 15:46:57 +0100 Subject: [PATCH 06/10] Use (CU-initiated) F1 Reset in stack --- openair2/F1AP/f1ap_cu_interface_management.c | 22 ++++++++++++++++++++ openair2/F1AP/f1ap_cu_interface_management.h | 1 + openair2/F1AP/f1ap_cu_task.c | 6 ++++++ openair2/RRC/NR/mac_rrc_dl_direct.c | 4 ++-- openair2/RRC/NR/mac_rrc_dl_f1ap.c | 7 +++++-- 5 files changed, 36 insertions(+), 4 deletions(-) diff --git a/openair2/F1AP/f1ap_cu_interface_management.c b/openair2/F1AP/f1ap_cu_interface_management.c index b81d81c6ba..8a6fd6311d 100644 --- a/openair2/F1AP/f1ap_cu_interface_management.c +++ b/openair2/F1AP/f1ap_cu_interface_management.c @@ -47,6 +47,28 @@ int CU_send_RESET_ACKNOWLEDGE(sctp_assoc_t assoc_id, const f1ap_reset_ack_t *ack AssertFatal(1 == 0, "Not implemented yet\n"); } +void CU_send_RESET(sctp_assoc_t assoc_id, const f1ap_reset_t *reset) +{ + uint8_t *buffer = NULL; + uint32_t len = 0; + + /* Encode F1 Reset */ + F1AP_F1AP_PDU_t *pdu = encode_f1ap_reset(reset); + if (pdu == NULL) { + LOG_E(F1AP, "failed to create asn.1 message for f1ap reset\n"); + return; + } + + /* encode ASN.1 PER */ + int encoded = f1ap_encode_pdu(pdu, &buffer, &len); + ASN_STRUCT_FREE(asn_DEF_F1AP_F1AP_PDU, pdu); + if (encoded <= 0) { + LOG_E(F1AP, "Failed to encode F1 reset\n"); + return; + } + f1ap_itti_send_sctp_data_req(assoc_id, buffer, len); +} + /** * @brief F1AP Setup Request decoding (9.2.1.4 of 3GPP TS 38.473) and transfer to RRC */ diff --git a/openair2/F1AP/f1ap_cu_interface_management.h b/openair2/F1AP/f1ap_cu_interface_management.h index 9d0bf4a391..fb39f1ca53 100644 --- a/openair2/F1AP/f1ap_cu_interface_management.h +++ b/openair2/F1AP/f1ap_cu_interface_management.h @@ -36,6 +36,7 @@ /* * Reset */ +void CU_send_RESET(sctp_assoc_t assoc_id, const f1ap_reset_t *reset); int CU_handle_RESET_ACKNOWLEDGE(instance_t instance, sctp_assoc_t assoc_id, uint32_t stream, F1AP_F1AP_PDU_t *pdu); int CU_send_RESET_ACKNOWLEDGE(sctp_assoc_t assoc_id, const f1ap_reset_ack_t *ack); diff --git a/openair2/F1AP/f1ap_cu_task.c b/openair2/F1AP/f1ap_cu_task.c index fa77f818ea..deb272776f 100644 --- a/openair2/F1AP/f1ap_cu_task.c +++ b/openair2/F1AP/f1ap_cu_task.c @@ -35,6 +35,7 @@ #include "f1ap_cu_rrc_message_transfer.h" #include "f1ap_cu_ue_context_management.h" #include "lib/f1ap_rrc_message_transfer.h" +#include "lib/f1ap_interface_management.h" #include "f1ap_cu_paging.h" #include "f1ap_cu_task.h" #include @@ -151,6 +152,11 @@ void *F1AP_CU_task(void *arg) { &received_msg->ittiMsg.sctp_data_ind); break; + case F1AP_RESET: + CU_send_RESET(assoc_id, &F1AP_RESET(received_msg)); + free_f1ap_reset(&F1AP_RESET(received_msg)); + break; + case F1AP_RESET_ACK: CU_send_RESET_ACKNOWLEDGE(assoc_id, &F1AP_RESET_ACK(received_msg)); break; diff --git a/openair2/RRC/NR/mac_rrc_dl_direct.c b/openair2/RRC/NR/mac_rrc_dl_direct.c index 77113f6c8b..4f8d45ac8c 100644 --- a/openair2/RRC/NR/mac_rrc_dl_direct.c +++ b/openair2/RRC/NR/mac_rrc_dl_direct.c @@ -30,8 +30,8 @@ static void f1_reset_cu_initiated_direct(sctp_assoc_t assoc_id, const f1ap_reset_t *reset) { - (void)reset; - AssertFatal(false, "%s() not implemented yet\n", __func__); + AssertFatal(assoc_id == -1, "illegal assoc_id %d\n", assoc_id); + f1_reset_cu_initiated(reset); } static void f1_reset_acknowledge_du_initiated_direct(sctp_assoc_t assoc_id, const f1ap_reset_ack_t *ack) diff --git a/openair2/RRC/NR/mac_rrc_dl_f1ap.c b/openair2/RRC/NR/mac_rrc_dl_f1ap.c index 8593ff816a..8a84a7f85d 100644 --- a/openair2/RRC/NR/mac_rrc_dl_f1ap.c +++ b/openair2/RRC/NR/mac_rrc_dl_f1ap.c @@ -35,8 +35,11 @@ static void f1_reset_cu_initiated_f1ap(sctp_assoc_t assoc_id, const f1ap_reset_t *reset) { - (void)reset; - AssertFatal(false, "%s() not implemented yet\n", __func__); + MessageDef *msg = itti_alloc_new_message(TASK_RRC_GNB, 0, F1AP_RESET); + msg->ittiMsgHeader.originInstance = assoc_id; + f1ap_reset_t *f1ap_msg = &F1AP_RESET(msg); + *f1ap_msg = cp_f1ap_reset(reset); + itti_send_msg_to_task(TASK_CU_F1, 0, msg); } static void f1_reset_acknowledge_du_initiated_f1ap(sctp_assoc_t assoc_id, const f1ap_reset_ack_t *ack) From df365d9a2dbd0ac31f7c389cdef4e95008323356 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 10 Feb 2025 15:41:44 +0100 Subject: [PATCH 07/10] Add F1 Reset Ack enc/dec/cp/eq/free library and test --- openair2/COMMON/f1ap_messages_types.h | 10 +- openair2/F1AP/lib/f1ap_interface_management.c | 115 ++++++++++++++++++ openair2/F1AP/lib/f1ap_interface_management.h | 7 ++ openair2/F1AP/tests/f1ap_lib_test.c | 45 +++++++ 4 files changed, 171 insertions(+), 6 deletions(-) diff --git a/openair2/COMMON/f1ap_messages_types.h b/openair2/COMMON/f1ap_messages_types.h index e72741f6ed..53740c1bde 100644 --- a/openair2/COMMON/f1ap_messages_types.h +++ b/openair2/COMMON/f1ap_messages_types.h @@ -578,12 +578,10 @@ typedef struct f1ap_reset_t { } f1ap_reset_t; typedef struct f1ap_reset_ack_t { - uint64_t transaction_id; - struct { - uint32_t gNB_CU_ue_id; - uint32_t gNB_DU_ue_id; - } ue_to_reset[F1AP_MAX_NO_OF_INDIVIDUAL_CONNECTIONS_TO_RESET]; - uint16_t criticality_diagnostics; + uint64_t transaction_id; + int num_ue_to_reset; + f1ap_ue_to_reset_t *ue_to_reset; // array of num_ue_to_reset elements + //uint16_t criticality_diagnostics; // not implemented as of now } f1ap_reset_ack_t; #endif /* F1AP_MESSAGES_TYPES_H_ */ diff --git a/openair2/F1AP/lib/f1ap_interface_management.c b/openair2/F1AP/lib/f1ap_interface_management.c index ce7ece3c11..08f324a618 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.c +++ b/openair2/F1AP/lib/f1ap_interface_management.c @@ -252,6 +252,121 @@ f1ap_reset_t cp_f1ap_reset(const f1ap_reset_t *orig) return cp; } +/* @brief encode F1 Reset Ack (9.2.1.2 in TS 38.473) */ +struct F1AP_F1AP_PDU *encode_f1ap_reset_ack(const f1ap_reset_ack_t *msg) +{ + F1AP_F1AP_PDU_t *pdu = calloc_or_fail(1, sizeof(*pdu)); + /* Create Message Type */ + pdu->present = F1AP_F1AP_PDU_PR_successfulOutcome; + asn1cCalloc(pdu->choice.successfulOutcome, so); + so->procedureCode = F1AP_ProcedureCode_id_Reset; + so->criticality = F1AP_Criticality_reject; + so->value.present = F1AP_SuccessfulOutcome__value_PR_ResetAcknowledge; + F1AP_ResetAcknowledge_t *reset_ack = &so->value.choice.ResetAcknowledge; + + /* (M) Transaction ID */ + asn1cSequenceAdd(reset_ack->protocolIEs.list, F1AP_ResetAcknowledgeIEs_t, ieC1); + ieC1->id = F1AP_ProtocolIE_ID_id_TransactionID; + ieC1->criticality = F1AP_Criticality_reject; + ieC1->value.present = F1AP_ResetAcknowledgeIEs__value_PR_TransactionID; + ieC1->value.choice.TransactionID = msg->transaction_id; + + /* TODO criticality diagnostics */ + + /* 0:N UEs to reset */ + if (msg->num_ue_to_reset == 0) + return pdu; /* no UEs to encode */ + + asn1cSequenceAdd(reset_ack->protocolIEs.list, F1AP_ResetAcknowledgeIEs_t, ieC2); + ieC2->id = F1AP_ProtocolIE_ID_id_UE_associatedLogicalF1_ConnectionListResAck; + ieC2->criticality = F1AP_Criticality_ignore; + ieC2->value.present = F1AP_ResetAcknowledgeIEs__value_PR_UE_associatedLogicalF1_ConnectionListResAck; + F1AP_UE_associatedLogicalF1_ConnectionListResAck_t *ue_to_reset = &ieC2->value.choice.UE_associatedLogicalF1_ConnectionListResAck; + for (int i = 0; i < msg->num_ue_to_reset; ++i) { + asn1cSequenceAdd(ue_to_reset->list, F1AP_UE_associatedLogicalF1_ConnectionItemResAck_t, conn_it_res); + conn_it_res->id = F1AP_ProtocolIE_ID_id_UE_associatedLogicalF1_ConnectionItem; + conn_it_res->criticality = F1AP_Criticality_ignore; + conn_it_res->value.present = F1AP_UE_associatedLogicalF1_ConnectionItemResAck__value_PR_UE_associatedLogicalF1_ConnectionItem; + conn_it_res->value.choice.UE_associatedLogicalF1_ConnectionItem = encode_f1ap_ue_to_reset(&msg->ue_to_reset[i]); + } + return pdu; +} + +/* @brief decode F1 Reset Ack (9.2.1.2 in TS 38.473) */ +bool decode_f1ap_reset_ack(const struct F1AP_F1AP_PDU *pdu, f1ap_reset_ack_t *out) +{ + _F1_EQ_CHECK_INT(pdu->present, F1AP_F1AP_PDU_PR_successfulOutcome); + AssertError(pdu->choice.successfulOutcome != NULL, return false, "pdu->choice.initiatingMessage is NULL"); + _F1_EQ_CHECK_LONG(pdu->choice.successfulOutcome->procedureCode, F1AP_ProcedureCode_id_Reset); + _F1_EQ_CHECK_INT(pdu->choice.successfulOutcome->value.present, F1AP_SuccessfulOutcome__value_PR_ResetAcknowledge); + + /* Check presence of mandatory IEs */ + F1AP_ResetAcknowledge_t *in = &pdu->choice.successfulOutcome->value.choice.ResetAcknowledge; + F1AP_ResetAcknowledgeIEs_t *ie; + F1AP_LIB_FIND_IE(F1AP_ResetAcknowledgeIEs_t, ie, in, F1AP_ProtocolIE_ID_id_TransactionID, true); + + /* Loop over all IEs */ + for (int i = 0; i < in->protocolIEs.list.count; i++) { + AssertError(in->protocolIEs.list.array[i] != NULL, return false, "in->protocolIEs.list.array[i] is NULL"); + ie = in->protocolIEs.list.array[i]; + switch (ie->id) { + case F1AP_ProtocolIE_ID_id_TransactionID: + // (M) Transaction ID + _F1_EQ_CHECK_INT(ie->value.present, F1AP_ResetAcknowledgeIEs__value_PR_TransactionID); + out->transaction_id = ie->value.choice.TransactionID; + break; + case F1AP_ProtocolIE_ID_id_UE_associatedLogicalF1_ConnectionListResAck: + _F1_EQ_CHECK_INT(ie->value.present, F1AP_ResetAcknowledgeIEs__value_PR_UE_associatedLogicalF1_ConnectionListResAck); + { + const F1AP_UE_associatedLogicalF1_ConnectionListResAck_t *conn_list = &ie->value.choice.UE_associatedLogicalF1_ConnectionListResAck; + AssertError(conn_list->list.count > 0, return false, "no UEs for partially reset F1 interface\n"); + out->num_ue_to_reset = conn_list->list.count; + out->ue_to_reset = calloc_or_fail(out->num_ue_to_reset, sizeof(*out->ue_to_reset)); + for (int i = 0; i < out->num_ue_to_reset; ++i) { + const F1AP_UE_associatedLogicalF1_ConnectionItemResAck_t *it_res = (const F1AP_UE_associatedLogicalF1_ConnectionItemResAck_t *)conn_list->list.array[i]; + _F1_EQ_CHECK_LONG(it_res->id, F1AP_ProtocolIE_ID_id_UE_associatedLogicalF1_ConnectionItem); + _F1_EQ_CHECK_INT(it_res->value.present, F1AP_UE_associatedLogicalF1_ConnectionItemResAck__value_PR_UE_associatedLogicalF1_ConnectionItem); + out->ue_to_reset[i] = decode_f1ap_ue_to_reset(&it_res->value.choice.UE_associatedLogicalF1_ConnectionItem); + } + } + break; + default: + AssertError(true, return false, "Reset Acknowledge: ProtocolIE id %ld not implemented, ignoring IE\n", ie->id); + break; + } + } + return true; +} + +void free_f1ap_reset_ack(f1ap_reset_ack_t *msg) +{ + for (int i = 0; i < msg->num_ue_to_reset; ++i) + free_f1ap_ue_to_reset(&msg->ue_to_reset[i]); + free(msg->ue_to_reset); +} + +bool eq_f1ap_reset_ack(const f1ap_reset_ack_t *a, const f1ap_reset_ack_t *b) +{ + _F1_EQ_CHECK_LONG(a->transaction_id, b->transaction_id); + _F1_EQ_CHECK_INT(a->num_ue_to_reset, b->num_ue_to_reset); + for (int i = 0; i < a->num_ue_to_reset; ++i) { + if (!eq_f1ap_ue_to_reset(&a->ue_to_reset[i], &b->ue_to_reset[i])) + return false; + } + return true; +} + +f1ap_reset_ack_t cp_f1ap_reset_ack(const f1ap_reset_ack_t *orig) +{ + f1ap_reset_ack_t cp = {.transaction_id = orig->transaction_id, .num_ue_to_reset = orig->num_ue_to_reset}; + if (cp.num_ue_to_reset > 0) { + cp.ue_to_reset = calloc_or_fail(cp.num_ue_to_reset, sizeof(*cp.ue_to_reset)); + for (int i = 0; i < cp.num_ue_to_reset; ++i) + cp.ue_to_reset[i] = cp_f1ap_ue_to_reset(&orig->ue_to_reset[i]); + } + return cp; +} + static const int nrb_lut[29] = {11, 18, 24, 25, 31, 32, 38, 51, 52, 65, 66, 78, 79, 93, 106, 107, 121, 132, 133, 135, 160, 162, 189, 216, 217, 245, 264, 270, 273}; diff --git a/openair2/F1AP/lib/f1ap_interface_management.h b/openair2/F1AP/lib/f1ap_interface_management.h index b3769eabfe..1b484f3476 100644 --- a/openair2/F1AP/lib/f1ap_interface_management.h +++ b/openair2/F1AP/lib/f1ap_interface_management.h @@ -34,6 +34,13 @@ void free_f1ap_reset(f1ap_reset_t *msg); bool eq_f1ap_reset(const f1ap_reset_t *a, const f1ap_reset_t *b); f1ap_reset_t cp_f1ap_reset(const f1ap_reset_t *orig); +/* F1 Reset Ack */ +struct F1AP_F1AP_PDU *encode_f1ap_reset_ack(const f1ap_reset_ack_t *msg); +bool decode_f1ap_reset_ack(const struct F1AP_F1AP_PDU *pdu, f1ap_reset_ack_t *out); +void free_f1ap_reset_ack(f1ap_reset_ack_t *msg); +bool eq_f1ap_reset_ack(const f1ap_reset_ack_t *a, const f1ap_reset_ack_t *b); +f1ap_reset_ack_t cp_f1ap_reset_ack(const f1ap_reset_ack_t *orig); + struct F1AP_F1AP_PDU *encode_f1ap_setup_request(const f1ap_setup_req_t *msg); bool decode_f1ap_setup_request(const struct F1AP_F1AP_PDU *pdu, f1ap_setup_req_t *out); f1ap_setup_req_t cp_f1ap_setup_request(const f1ap_setup_req_t *msg); diff --git a/openair2/F1AP/tests/f1ap_lib_test.c b/openair2/F1AP/tests/f1ap_lib_test.c index 2e516a5607..29d4b625ed 100644 --- a/openair2/F1AP/tests/f1ap_lib_test.c +++ b/openair2/F1AP/tests/f1ap_lib_test.c @@ -447,6 +447,50 @@ static void test_f1ap_reset_part(void) free_f1ap_reset(&orig); } +/** + * @brief Test F1 reset ack with differently ack'd UEs ("generic ack" is + * special case with no individual UE acked) + */ +static void test_f1ap_reset_ack(void) +{ + f1ap_reset_ack_t orig = { + .transaction_id = 4, + .num_ue_to_reset = 4, + }; + orig.ue_to_reset = calloc_or_fail(orig.num_ue_to_reset, sizeof(*orig.ue_to_reset)); + orig.ue_to_reset[0].gNB_CU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[0].gNB_CU_ue_id)); + *orig.ue_to_reset[0].gNB_CU_ue_id = 10; + orig.ue_to_reset[1].gNB_DU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[1].gNB_DU_ue_id)); + *orig.ue_to_reset[1].gNB_DU_ue_id = 11; + orig.ue_to_reset[2].gNB_CU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[2].gNB_CU_ue_id)); + *orig.ue_to_reset[2].gNB_CU_ue_id = 12; + orig.ue_to_reset[2].gNB_DU_ue_id = malloc_or_fail(sizeof(*orig.ue_to_reset[2].gNB_DU_ue_id)); + *orig.ue_to_reset[2].gNB_DU_ue_id = 13; + // orig.ue_to_reset[3] intentionally empty (because it is allowed, both IDs are optional) + + F1AP_F1AP_PDU_t *f1enc = encode_f1ap_reset_ack(&orig); + F1AP_F1AP_PDU_t *f1dec = f1ap_encode_decode(f1enc); + f1ap_msg_free(f1enc); + + f1ap_reset_ack_t decoded = {0}; + bool ret = decode_f1ap_reset_ack(f1dec, &decoded); + AssertFatal(ret, "decode_f1ap_reset_ack(): could not decode message\n"); + f1ap_msg_free(f1dec); + + ret = eq_f1ap_reset_ack(&orig, &decoded); + AssertFatal(ret, "eq_f1ap_reset_ack(): decoded message doesn't match original\n"); + free_f1ap_reset_ack(&decoded); + + f1ap_reset_ack_t cp = cp_f1ap_reset_ack(&orig); + ret = eq_f1ap_reset_ack(&orig, &cp); + AssertFatal(ret, "eq_f1ap_reset_ack(): copied message doesn't match original\n"); + free_f1ap_reset_ack(&cp); + + printf("f1ap_reset_ack successful\n"); + + free_f1ap_reset_ack(&orig); +} + /** * @brief Test F1 gNB-DU Configuration Update */ @@ -671,6 +715,7 @@ int main() test_f1ap_setup_failure(); test_f1ap_reset_all(); test_f1ap_reset_part(); + test_f1ap_reset_ack(); test_f1ap_du_configuration_update(); test_f1ap_cu_configuration_update(); test_f1ap_cu_configuration_update_acknowledge(); From ff106c41a38d74de8db426833bdfee208d5a6584 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 10 Feb 2025 15:59:10 +0100 Subject: [PATCH 08/10] Use (CU-initiated) F1 Reset Acknowledgement in stack --- openair2/F1AP/f1ap_cu_interface_management.c | 16 +++++++- openair2/F1AP/f1ap_du_interface_management.c | 38 ++++++------------- openair2/F1AP/f1ap_du_task.c | 2 + .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 5 +-- .../LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c | 6 ++- openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c | 3 +- openair2/RRC/NR/rrc_gNB.c | 5 +++ 7 files changed, 40 insertions(+), 35 deletions(-) diff --git a/openair2/F1AP/f1ap_cu_interface_management.c b/openair2/F1AP/f1ap_cu_interface_management.c index 8a6fd6311d..e697649fb5 100644 --- a/openair2/F1AP/f1ap_cu_interface_management.c +++ b/openair2/F1AP/f1ap_cu_interface_management.c @@ -39,7 +39,21 @@ int CU_handle_RESET_ACKNOWLEDGE(instance_t instance, sctp_assoc_t assoc_id, uint32_t stream, F1AP_F1AP_PDU_t *pdu) { - AssertFatal(1 == 0, "Not implemented yet\n"); + DevAssert(pdu != NULL); + + f1ap_reset_ack_t ack = {0}; + /* Decode */ + if (!decode_f1ap_reset_ack(pdu, &ack)) { + LOG_E(F1AP, "cannot decode F1AP Reset acknowledgement\n"); + free_f1ap_reset_ack(&ack); + return -1; + } + /* Send to RRC (ITTI) */ + MessageDef *message_p = itti_alloc_new_message(TASK_CU_F1, 0, F1AP_RESET_ACK); + message_p->ittiMsgHeader.originInstance = assoc_id; + F1AP_RESET_ACK(message_p) = ack; /* "move" message into ITTI, RRC thread will free it */ + itti_send_msg_to_task(TASK_RRC_GNB, GNB_MODULE_ID_TO_INSTANCE(instance), message_p); + return 0; } int CU_send_RESET_ACKNOWLEDGE(sctp_assoc_t assoc_id, const f1ap_reset_ack_t *ack) diff --git a/openair2/F1AP/f1ap_du_interface_management.c b/openair2/F1AP/f1ap_du_interface_management.c index 0d0c9c2946..7a892086b8 100644 --- a/openair2/F1AP/f1ap_du_interface_management.c +++ b/openair2/F1AP/f1ap_du_interface_management.c @@ -117,35 +117,21 @@ int DU_handle_RESET(instance_t instance, sctp_assoc_t assoc_id, uint32_t stream, int DU_send_RESET_ACKNOWLEDGE(sctp_assoc_t assoc_id, const f1ap_reset_ack_t *ack) { - F1AP_F1AP_PDU_t pdu= {0}; - uint8_t *buffer; - uint32_t len; - /* Create */ - /* 0. pdu Type */ - pdu.present = F1AP_F1AP_PDU_PR_successfulOutcome; - asn1cCalloc(pdu.choice.successfulOutcome, successMsg); - successMsg->procedureCode = F1AP_ProcedureCode_id_Reset; - successMsg->criticality = F1AP_Criticality_reject; - successMsg->value.present = F1AP_SuccessfulOutcome__value_PR_ResetAcknowledge; - F1AP_ResetAcknowledge_t *f1ResetAcknowledge = &successMsg->value.choice.ResetAcknowledge; - /* mandatory */ - /* c1. Transaction ID (integer value) */ - asn1cSequenceAdd(f1ResetAcknowledge->protocolIEs.list, F1AP_ResetAcknowledgeIEs_t, ieC1); - ieC1->id = F1AP_ProtocolIE_ID_id_TransactionID; - ieC1->criticality = F1AP_Criticality_reject; - ieC1->value.present = F1AP_ResetAcknowledgeIEs__value_PR_TransactionID; - ieC1->value.choice.TransactionID = ack->transaction_id; - - /* TODO: (Optional) partialF1Interface, criticality diagnostics */ - - /* encode */ - if (f1ap_encode_pdu(&pdu, &buffer, &len) < 0) { - LOG_E(F1AP, "Failed to encode F1ResetAcknowledge\n"); + F1AP_F1AP_PDU_t *pdu = encode_f1ap_reset_ack(ack); + if (!pdu) { + LOG_E(F1AP, "failed to create ASN.1 message for reset acknowledge\n"); return -1; } - /* send */ - ASN_STRUCT_RESET(asn_DEF_F1AP_F1AP_PDU, &pdu); + /* encode */ + uint8_t *buffer; + uint32_t len; + int encoded = f1ap_encode_pdu(pdu, &buffer, &len); + ASN_STRUCT_FREE(asn_DEF_F1AP_F1AP_PDU, pdu); + if (encoded <= 0) { + LOG_E(F1AP, "Failed to encode F1ResetAcknowledge\n"); + return -1; + } f1ap_itti_send_sctp_data_req(assoc_id, buffer, len); return 0; } diff --git a/openair2/F1AP/f1ap_du_task.c b/openair2/F1AP/f1ap_du_task.c index 0d675a76e1..c231561eec 100644 --- a/openair2/F1AP/f1ap_du_task.c +++ b/openair2/F1AP/f1ap_du_task.c @@ -35,6 +35,7 @@ #include "f1ap_du_ue_context_management.h" #include "f1ap_du_rrc_message_transfer.h" #include "lib/f1ap_rrc_message_transfer.h" +#include "lib/f1ap_interface_management.h" #include "f1ap_du_task.h" #include @@ -150,6 +151,7 @@ void *F1AP_DU_task(void *arg) { case F1AP_RESET_ACK: DU_send_RESET_ACKNOWLEDGE(assoc_id, &F1AP_RESET_ACK(msg)); + free_f1ap_reset_ack(&F1AP_RESET_ACK(msg)); break; case F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE: diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index 124d63d293..f92559c4ee 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -125,12 +125,9 @@ void f1_reset_cu_initiated(const f1ap_reset_t *reset) { LOG_I(MAC, "F1 Reset initiated by CU\n"); - f1ap_reset_ack_t ack = {0}; + f1ap_reset_ack_t ack = {.transaction_id = reset->transaction_id}; if(reset->reset_type == F1AP_RESET_ALL) { du_clear_all_ue_states(); - ack = (f1ap_reset_ack_t) { - .transaction_id = reset->transaction_id - }; } else { // reset->reset_type == F1AP_RESET_PART_OF_F1_INTERFACE AssertFatal(1==0, "Not implemented yet\n"); diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c index ad8978d514..e687991a5e 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_direct.c @@ -34,8 +34,10 @@ static void f1_reset_du_initiated_direct(const f1ap_reset_t *reset) static void f1_reset_acknowledge_cu_initiated_direct(const f1ap_reset_ack_t *ack) { - (void) ack; - AssertFatal(false, "%s() not implemented yet\n", __func__); + MessageDef *msg = itti_alloc_new_message(TASK_MAC_GNB, 0, F1AP_RESET_ACK); + msg->ittiMsgHeader.originInstance = -1; // means monolithic + F1AP_RESET_ACK(msg) = cp_f1ap_reset_ack(ack); + itti_send_msg_to_task(TASK_RRC_GNB, 0, msg); } static void f1_setup_request_direct(const f1ap_setup_req_t *req) diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c index 544ab3889c..81373e0bc9 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_ul_f1ap.c @@ -61,8 +61,7 @@ static void f1_reset_du_initiated_f1ap(const f1ap_reset_t *reset) static void f1_reset_acknowledge_cu_initiated_f1ap(const f1ap_reset_ack_t *ack) { MessageDef *msg = itti_alloc_new_message(TASK_MAC_GNB, 0, F1AP_RESET_ACK); - f1ap_reset_ack_t *f1ap_msg = &F1AP_RESET_ACK(msg); - *f1ap_msg = *ack; + F1AP_RESET_ACK(msg) = cp_f1ap_reset_ack(ack); itti_send_msg_to_task(TASK_DU_F1, 0, msg); } diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index fd69a4fafd..e8d65e5731 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2607,6 +2607,11 @@ void *rrc_gnb_task(void *args_p) { LOG_E(NR_RRC, "Handling of F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE not implemented\n"); break; + case F1AP_RESET_ACK: + LOG_I(NR_RRC, "received F1AP reset acknowledgement\n"); + free_f1ap_reset_ack(&F1AP_RESET_ACK(msg_p)); + break; + /* Messages from X2AP */ case X2AP_ENDC_SGNB_ADDITION_REQ: LOG_I(NR_RRC, "Received ENDC sgNB addition request from X2AP \n"); From d2bb779e782f8f4a97759f145ad519d001e2ed3e Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 10 Feb 2025 17:14:13 +0100 Subject: [PATCH 09/10] Refactor rrc_remove_ue() to not send NGAP ctxt release cplt The next commit will use rrc_remove_ue() to delete a CU(-CP+-UP) context. However, we don't send NGAP UE context release at that moment (we will instead *request* the release); more generally, it's not clear that every user of rrc_remove_ue() wants to actually send this message. Hence, refactor the code by separately calling rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE() where appropriate. --- openair2/RRC/NR/rrc_gNB.c | 7 +++---- openair2/RRC/NR/rrc_gNB_NGAP.c | 3 +++ openair2/RRC/NR/rrc_gNB_radio_bearers.c | 6 ++++++ openair2/RRC/NR/rrc_gNB_radio_bearers.h | 3 +++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/openair2/RRC/NR/rrc_gNB.c b/openair2/RRC/NR/rrc_gNB.c index e8d65e5731..358b5af653 100644 --- a/openair2/RRC/NR/rrc_gNB.c +++ b/openair2/RRC/NR/rrc_gNB.c @@ -2074,10 +2074,6 @@ void rrc_remove_ue(gNB_RRC_INST *rrc, rrc_gNB_ue_context_t *ue_context_p) * are in E1, we also need to free the UE in the CU-CP, so call it twice to * cover all cases */ nr_pdcp_remove_UE(UE->rrc_ue_id); - uint32_t pdu_sessions[256]; - for (int i = 0; i < UE->nb_of_pdusessions && i < 256; ++i) - pdu_sessions[i] = UE->pduSession[i].param.pdusession_id; - rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE(0, UE->rrc_ue_id, UE->nb_of_pdusessions, pdu_sessions); LOG_I(NR_RRC, "removed UE CU UE ID %u/RNTI %04x \n", UE->rrc_ue_id, UE->rnti); rrc_delete_ue_data(UE); rrc_gNB_remove_ue_context(rrc, ue_context_p); @@ -2099,6 +2095,9 @@ static void rrc_CU_process_ue_context_release_complete(MessageDef *msg_p) /* only trigger release if it has been requested by core * otherwise, it might be CU that requested release on a DU during normal * operation (i.e, handover) */ + uint32_t pdu_sessions[NGAP_MAX_PDU_SESSION]; + get_pduSession_array(UE, pdu_sessions); + rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE(0, UE->rrc_ue_id, UE->nb_of_pdusessions, pdu_sessions); rrc_remove_ue(RC.nrrrc[0], ue_context_p); } } diff --git a/openair2/RRC/NR/rrc_gNB_NGAP.c b/openair2/RRC/NR/rrc_gNB_NGAP.c index 0b1c3012f4..12ef7ec2f4 100644 --- a/openair2/RRC/NR/rrc_gNB_NGAP.c +++ b/openair2/RRC/NR/rrc_gNB_NGAP.c @@ -1231,6 +1231,9 @@ int rrc_gNB_process_NGAP_UE_CONTEXT_RELEASE_COMMAND(MessageDef *msg_p, instance_ /* UE will be freed after UE context release complete */ } else { // the DU is offline already + uint32_t pdu_sessions[NGAP_MAX_PDU_SESSION]; + get_pduSession_array(UE, pdu_sessions); + rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_COMPLETE(0, UE->rrc_ue_id, UE->nb_of_pdusessions, pdu_sessions); rrc_remove_ue(rrc, ue_context_p); } diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.c b/openair2/RRC/NR/rrc_gNB_radio_bearers.c index ce6eb40bbe..8255ccdb38 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.c +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.c @@ -58,6 +58,12 @@ rrc_pdu_session_param_t *find_pduSession_from_drbId(gNB_RRC_UE_t *ue, int drb_id return find_pduSession(ue, id, false); } +void get_pduSession_array(gNB_RRC_UE_t *ue, uint32_t pdu_sessions[NGAP_MAX_PDU_SESSION]) +{ + for (int i = 0; i < ue->nb_of_pdusessions && i < NGAP_MAX_PDU_SESSION; ++i) + pdu_sessions[i] = ue->pduSession[i].param.pdusession_id; +} + drb_t *get_drb(gNB_RRC_UE_t *ue, uint8_t drb_id) { DevAssert(drb_id > 0 && drb_id <= 32); diff --git a/openair2/RRC/NR/rrc_gNB_radio_bearers.h b/openair2/RRC/NR/rrc_gNB_radio_bearers.h index 37ddbdd431..133e17627f 100644 --- a/openair2/RRC/NR/rrc_gNB_radio_bearers.h +++ b/openair2/RRC/NR/rrc_gNB_radio_bearers.h @@ -73,6 +73,9 @@ rrc_pdu_session_param_t *find_pduSession(gNB_RRC_UE_t *ue, int id, bool create); /// @brief get PDU session of UE ue through the DRB drb_id rrc_pdu_session_param_t *find_pduSession_from_drbId(gNB_RRC_UE_t *ue, int drb_id); +/// @brief get the PDU sessions of this UE in a single array +void get_pduSession_array(gNB_RRC_UE_t *ue, uint32_t pdu_sessions[NGAP_MAX_PDU_SESSION]); + /// @brief set PDCP configuration in a bearer context management message void set_bearer_context_pdcp_config(bearer_context_pdcp_config_t *pdcp_config, drb_t *rrc_drb, bool um_on_default_drb); From e8661f5ae5d51cf1ab07ad98eef2b563f8fedc39 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Sat, 8 Feb 2025 13:06:30 +0100 Subject: [PATCH 10/10] Reset CU-CP E1 UE context after E1 Setup Response As in deba3ae066df ("Reset CU-UP E1 UE contexts after E1 Setup Response"), the CU-CP should reset the E1 UE contexts after E1 Setup Response. 38.463 sec. 8.2.3 says > This procedure also re-initialises the E1AP UE-related contexts (if > any) and erases all related signalling connections in the two nodes like > a Reset procedure would do. In the case of CU-CP, this is a bit more complicated. More precisely, we need to wait for the CU-UP to be available again before informing the DU about the loss (implemented via an F1 Reset message), because otherwise, the UE might reconnect immediately, before the CU-UP is online again. Hence, on connection loss, we mark affected UEs (by setting their E1 association ID to 0); as long as the CU-UP does not connect, nothing will happen (but UEs don't have user plane). Upon CU-UP connecting again, we request release of the UEs via NGAP (similarly to what is done if the DU goes offline, see invalidate_du_connections()), free the UE contexts locally, and send an F1 Reset message to the DU, because 38.473 sec 8.2.1.2.1 says > a failure at the gNB-CU [...] has resulted in the loss of some or > all transaction reference information [...] a RESET message shall be > sent to the gNB-DU. The core should accept and send an NGAP release command message; since no UE contexts are to be found (already deleted), the CU-CP will just send a release complete (together with some warnings). It might be possible to more gracefully handle the situation (e.g., as described in 38.401 section 8.9.5 "Change of gNB-CU-UP"), but - we lack the PDCP SN exchange - the current procedure has the advantage of being equivalent to "restart of the entire gNB", with less downtown. --- openair2/RRC/NR/rrc_gNB_cuup.c | 91 +++++++++++++++++++++++++++++++++- openair2/RRC/NR/rrc_gNB_du.c | 11 ++++ openair2/RRC/NR/rrc_gNB_du.h | 2 + 3 files changed, 103 insertions(+), 1 deletion(-) diff --git a/openair2/RRC/NR/rrc_gNB_cuup.c b/openair2/RRC/NR/rrc_gNB_cuup.c index 689670fa85..c065299824 100644 --- a/openair2/RRC/NR/rrc_gNB_cuup.c +++ b/openair2/RRC/NR/rrc_gNB_cuup.c @@ -24,13 +24,17 @@ #include #include #include -#include "PHY/defs_common.h" + +#include "common/utils/alg/find.h" +#include "common/utils/alg/foreach.h" #include "RRC/NR/nr_rrc_proto.h" #include "T.h" #include "as_message.h" #include "assertions.h" #include "common/ran_context.h" #include "common/utils/T/T.h" +#include "rrc_gNB_UE_context.h" +#include "rrc_gNB_du.h" #include "e1ap_messages_types.h" #include "intertask_interface.h" #include "nr_rrc_defs.h" @@ -38,6 +42,7 @@ #include "rrc_messages_types.h" #include "tree.h" #include "e1ap_interface_management.h" +#include "rrc_gNB_NGAP.h" static int cuup_compare(const nr_rrc_cuup_container_t *a, const nr_rrc_cuup_container_t *b) { @@ -168,6 +173,69 @@ static void e1ap_setup_failure(sctp_assoc_t assoc_id, uint64_t transac_id, e1ap_ itti_send_msg_to_task(TASK_CUCP_E1, 0 /*unused by callee*/, msg_p); } +static bool has_assoc_id(const void *vval, const void *vit) +{ + sctp_assoc_t val = *(sctp_assoc_t *)vval; + sctp_assoc_t it = *(sctp_assoc_t *)vit; + return val == it; +} + +/* @brief Remove RRC UE contexts of UE without E1 connection. + * + * 38.463 sec 8.2.3. says that "[E1 Setup] procedure also re-initialises the + * E1AP UE-related contexts (if any) and erases all related signalling + * connections in the two nodes like a Reset procedure would do.". If the CU-UP + * reconnects, it's previous UE contexts are here, but not associated. To + * fulfil the above, we simply free all UE contexts that don't have a CU-UP, + * and send a F1 Reset message to the corresponding DU(s), following the fact + * that (38.473 sec 8.2.1.2.1) "a failure at the gNB-CU [...] has resulted in + * the loss of some or all transaction reference information[...] a RESET + * message shall be sent to the gNB-DU.". This might be overarching in that UEs + * that are in the process of connecting or that are on another DU might be + * affected by that reset procedure as well; we suppose they will connect later + * again. */ +static void remove_unassociated_e1_connections(gNB_RRC_INST *rrc, sctp_assoc_t e1_assoc_id) +{ + seq_arr_t affected_du; + seq_arr_init(&affected_du, sizeof(sctp_assoc_t)); + seq_arr_t ue_context_to_remove; + seq_arr_init(&ue_context_to_remove, sizeof(rrc_gNB_ue_context_t *)); + + rrc_gNB_ue_context_t *ue_context_p = NULL; + RB_FOREACH(ue_context_p, rrc_nr_ue_tree_s, &rrc->rrc_ue_head) { + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + uint32_t ue_id = UE->rrc_ue_id; + if (ue_associated_to_cuup(rrc, UE)) + continue; + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); + elm_arr_t ret = find_if(&affected_du, &ue_data.du_assoc_id, has_assoc_id); + if (!ret.found) + seq_arr_push_back(&affected_du, &ue_data.du_assoc_id, sizeof(ue_data.du_assoc_id)); + + /* remove UE context: we store the pointer, so pass pointer to ue_context_p */ + seq_arr_push_back(&ue_context_to_remove, &ue_context_p, sizeof(ue_context_p)); + LOG_I(NR_RRC, "remove E1-unassociated UE context ID %d\n", ue_id); + } + + for (int i = 0; i < seq_arr_size(&ue_context_to_remove); ++i) { + /* we retrieve a pointer (=iterator) to the UE context pointer + * (ue_context_p), so dereference once */ + rrc_gNB_ue_context_t *p = *(rrc_gNB_ue_context_t **)seq_arr_at(&ue_context_to_remove, i); + ngap_cause_t cause = {.type = NGAP_CAUSE_RADIO_NETWORK, .value = NGAP_CAUSE_RADIO_NETWORK_RADIO_CONNECTION_WITH_UE_LOST}; + rrc_gNB_send_NGAP_UE_CONTEXT_RELEASE_REQ(0, p, cause); + rrc_remove_ue(rrc, p); + } + seq_arr_free(&ue_context_to_remove, NULL); + + for (int i = 0; i < seq_arr_size(&affected_du); ++i) { + void *it = seq_arr_at(&affected_du, i); + sctp_assoc_t du_assoc_id = *(sctp_assoc_t *)it; + LOG_W(NR_RRC, "trigger F1 reset on DU %d due to E1 connection loss\n", du_assoc_id); + trigger_f1_reset(rrc, du_assoc_id); + } + seq_arr_free(&affected_du, NULL); +} + /** * @brief E1AP Setup Request processing on CU-CP */ @@ -216,6 +284,8 @@ int rrc_gNB_process_e1_setup_req(sctp_assoc_t assoc_id, const e1ap_setup_req_t * RB_INSERT(rrc_cuup_tree, &rrc->cuups, cuup); rrc->num_cuups++; + remove_unassociated_e1_connections(rrc, assoc_id); + MessageDef *msg_p = itti_alloc_new_message(TASK_RRC_GNB, 0, E1AP_SETUP_RESP); msg_p->ittiMsgHeader.originInstance = assoc_id; e1ap_setup_resp_t *resp = &E1AP_SETUP_RESP(msg_p); @@ -225,6 +295,24 @@ int rrc_gNB_process_e1_setup_req(sctp_assoc_t assoc_id, const e1ap_setup_req_t * return 0; } +/* @brief invalidate (remove) all E1 assoc ID from UE contexts belonging to this CU-UP */ +static void invalidate_cuup_connections(gNB_RRC_INST *rrc, sctp_assoc_t e1_assoc_id) +{ + rrc_gNB_ue_context_t *ue_context_p = NULL; + RB_FOREACH(ue_context_p, rrc_nr_ue_tree_s, &rrc->rrc_ue_head) { + gNB_RRC_UE_t *UE = &ue_context_p->ue_context; + uint32_t ue_id = UE->rrc_ue_id; + f1_ue_data_t ue_data = cu_get_f1_ue_data(ue_id); + if (ue_data.e1_assoc_id != e1_assoc_id) + continue; /* UE is on another CU-UP */ + + ue_data.e1_assoc_id = 0; + bool success = cu_update_f1_ue_data(ue_id, &ue_data); + DevAssert(success); + LOG_W(NR_RRC, "CU-UP %d of UE %d is gone\n", e1_assoc_id, ue_id); + } +} + /** * @brief RRC Processing of the indication of E1 connection loss on CU-CP */ @@ -232,6 +320,7 @@ void rrc_gNB_process_e1_lost_connection(gNB_RRC_INST *rrc, e1ap_lost_connection_ { LOG_I(NR_RRC, "Received E1 connection loss indication on RRC\n"); AssertFatal(assoc_id != 0, "illegal assoc_id == 0: should be -1 (monolithic) or >0 (split)\n"); + invalidate_cuup_connections(rrc, assoc_id); nr_rrc_cuup_container_t e = {.assoc_id = assoc_id}; nr_rrc_cuup_container_t *cuup = RB_FIND(rrc_cuup_tree, &rrc->cuups, &e); if (cuup == NULL) { diff --git a/openair2/RRC/NR/rrc_gNB_du.c b/openair2/RRC/NR/rrc_gNB_du.c index 802735ee85..402e245dac 100644 --- a/openair2/RRC/NR/rrc_gNB_du.c +++ b/openair2/RRC/NR/rrc_gNB_du.c @@ -644,3 +644,14 @@ nr_rrc_du_container_t *find_target_du(gNB_RRC_INST *rrc, sctp_assoc_t source_ass } return target_du; } + +void trigger_f1_reset(gNB_RRC_INST *rrc, sctp_assoc_t du_assoc_id) +{ + f1ap_reset_t reset = { + .transaction_id = F1AP_get_next_transaction_identifier(0, 0), + .cause = F1AP_CAUSE_TRANSPORT, + .cause_value = 1, // F1AP_CauseTransport_transport_resource_unavailable + .reset_type = F1AP_RESET_ALL, // DU does not support partial reset yet + }; + rrc->mac_rrc.f1_reset(du_assoc_id, &reset); +} diff --git a/openair2/RRC/NR/rrc_gNB_du.h b/openair2/RRC/NR/rrc_gNB_du.h index aadac0845a..514416211f 100644 --- a/openair2/RRC/NR/rrc_gNB_du.h +++ b/openair2/RRC/NR/rrc_gNB_du.h @@ -60,4 +60,6 @@ struct nr_rrc_du_container_t *find_target_du(struct gNB_RRC_INST_s *rrc, sctp_as } \ } +void trigger_f1_reset(struct gNB_RRC_INST_s *rrc, sctp_assoc_t du_assoc_id); + #endif /* RRC_GNB_DU_H_ */