diff --git a/CMakeLists.txt b/CMakeLists.txt index d73952d85c..db7b8f59c0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -383,6 +383,12 @@ endif() add_boolean_option(ENABLE_IMSCOPE OFF "Enable phy scope based on imgui" OFF) add_boolean_option(ENABLE_IMSCOPE_RECORD OFF "Enable recording IQ data for imscope" OFF) +######################### +##### E3 AGENT +######################### +set(E3_AGENT "OFF" CACHE STRING "O-RAN nGRG E3 Agent for dApps") +set_property(CACHE E3_AGENT PROPERTY STRINGS "ON" "OFF") + ################################################## # ASN.1 grammar C code generation & dependencies # ################################################## @@ -1830,6 +1836,11 @@ if(E2_AGENT) target_compile_definitions(nr-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT) endif() +if (E3_AGENT) + target_link_libraries(nr-softmodem PRIVATE e3ap) + target_compile_definitions(nr-softmodem PRIVATE E3_AGENT) +endif() + # force the generation of ASN.1 so that we don't need to wait during the build target_link_libraries(nr-softmodem PRIVATE diff --git a/cmake_targets/build_oai b/cmake_targets/build_oai index 52763a322e..a42c26a51e 100755 --- a/cmake_targets/build_oai +++ b/cmake_targets/build_oai @@ -43,6 +43,8 @@ Options: Sets build directory (will be /cmake_targets//build) --build-e2 Enable the the E2 Agent +--build-e3 + Enable the the E3 Agent -I | --install-external-packages Installs required packages such as LibXML, asn1.1 compiler, ... This option will require root password @@ -143,6 +145,10 @@ function main() { CMAKE_CMD="$CMAKE_CMD -DE2_AGENT=ON" shift ;; + --build-e3 ) + CMAKE_CMD="$CMAKE_CMD -DE3_AGENT=ON" + shift + ;; -I | --install-external-packages) INSTALL_EXTERNAL=1 echo_info "Will install external packages" diff --git a/common/utils/LOG/log.h b/common/utils/LOG/log.h index 77bc3481c0..5b199724fd 100644 --- a/common/utils/LOG/log.h +++ b/common/utils/LOG/log.h @@ -155,6 +155,7 @@ static const char *const flag_name[] = {FOREACH_FLAG(FLAG_TEXT) ""}; COMP_DEF(NFAPI_PNF, log) \ COMP_DEF(ITTI, log) \ COMP_DEF(UTIL, log) \ + COMP_DEF(E3AP, log) \ COMP_DEF(MAX_LOG_PREDEF_COMPONENTS, ) #define COMP_ENUM(comp, file_extension) comp, diff --git a/common/utils/T/T_messages.txt b/common/utils/T/T_messages.txt index 175b3b70bd..333236bef7 100644 --- a/common/utils/T/T_messages.txt +++ b/common/utils/T/T_messages.txt @@ -434,6 +434,27 @@ ID = LEGACY_MAC_TRACE GROUP = ALL:LEGACY_MAC:LEGACY_GROUP_TRACE:LEGACY FORMAT = string,log +ID = LEGACY_E3AP_INFO + DESC = E3AP legacy logs - info level + GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_INFO:LEGACY + FORMAT = string,log +ID = LEGACY_E3AP_ERROR + DESC = E3AP legacy logs - error level + GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_ERROR:LEGACY + FORMAT = string,log +ID = LEGACY_E3AP_WARNING + DESC = E3AP legacy logs - warning level + GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_WARNING:LEGACY + FORMAT = string,log +ID = LEGACY_E3AP_DEBUG + DESC = E3AP legacy logs - debug level + GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_DEBUG:LEGACY + FORMAT = string,log +ID = LEGACY_E3AP_TRACE + DESC = E3AP legacy logs - trace level + GROUP = ALL:LEGACY_E3AP:LEGACY_GROUP_TRACE:LEGACY + FORMAT = string,log + ID = LEGACY_NR_MAC_INFO DESC = NR_MAC legacy logs - info level GROUP = ALL:LEGACY_NR_MAC:LEGACY_GROUP_INFO:LEGACY diff --git a/executables/nr-softmodem.c b/executables/nr-softmodem.c index c1bd360001..42012853e1 100644 --- a/executables/nr-softmodem.c +++ b/executables/nr-softmodem.c @@ -19,6 +19,11 @@ unsigned short config_frames[4] = {2,9,11,13}; #include "openair2/E2AP/flexric/src/agent/e2_agent_api.h" #include "openair2/E2AP/RAN_FUNCTION/init_ran_func.h" #endif + +#ifdef E3_AGENT +#include "openair2/E3AP/e3_agent.h" +#endif + #include "nr-softmodem.h" #include #include @@ -521,6 +526,13 @@ int main( int argc, char **argv ) { softmodem_verify_mode(get_softmodem_params()); +////////////////////////////////// +//// Init the E3 Agent +#ifdef E3_AGENT + printf("Init E3 Agent\n"); + e3_init(); +#endif // E3_AGENT + #if T_TRACER T_Config_Init(); #endif @@ -727,6 +739,11 @@ int main( int argc, char **argv ) { time_manager_finish(); +#ifdef E3_AGENT + printf("Destroy E3 Agent\n"); + e3_destroy(); +#endif // E3_AGENT + free(pckg); logClean(); printf("Bye.\n"); diff --git a/openair2/CMakeLists.txt b/openair2/CMakeLists.txt index 8d1238c3e5..3fe216851e 100644 --- a/openair2/CMakeLists.txt +++ b/openair2/CMakeLists.txt @@ -5,6 +5,10 @@ add_subdirectory(E1AP) if(E2_AGENT) add_subdirectory(E2AP) endif() +if(E3_AGENT) + message(STATUS "Add E3 Agent capabilities") + add_subdirectory(E3AP) +endif() add_subdirectory(F1AP) add_subdirectory(LAYER2) add_subdirectory(M2AP) diff --git a/openair2/E3AP/CMakeLists.txt b/openair2/E3AP/CMakeLists.txt new file mode 100644 index 0000000000..4ec2da846b --- /dev/null +++ b/openair2/E3AP/CMakeLists.txt @@ -0,0 +1,44 @@ +# SPDX-License-Identifier: LicenseRef-CSSL-1.0 + +find_package(PkgConfig REQUIRED) +pkg_check_modules(CLIBE3 REQUIRED libe3) + +if(NOT CLIBE3_FOUND) + message(FATAL_ERROR "libe3 not found and required for E3 Agent") +endif() + +message(STATUS "Using for E3AP libe3 version: ${CLIBE3_VERSION}") + +include_directories(${CLIBE3_INCLUDE_DIRS}) + +# Set default encoding format if not specified +if(NOT DEFINED E3_ENCODING_FORMAT) + set(E3_ENCODING_FORMAT "ASN1" CACHE STRING "E3AP encoding format (ASN1 or JSON)") +endif() + +# Validate encoding format +if(NOT E3_ENCODING_FORMAT MATCHES "^(ASN1|JSON)$") + message(FATAL_ERROR "Invalid E3_ENCODING_FORMAT: ${E3_ENCODING_FORMAT}. Must be ASN1 or JSON") +endif() + +message(STATUS "E3AP encoding format: ${E3_ENCODING_FORMAT}") + +add_library(e3ap e3_agent.c config/e3_config.c) + +# target_link_libraries(e3ap PUBLIC OCP_ITTI) # TODO + +# Set encoding format compile definitions - both string value and format flags +target_compile_definitions(e3ap PRIVATE E3_ENCODING_FORMAT="${E3_ENCODING_FORMAT}") + +if(E3_ENCODING_FORMAT STREQUAL "ASN1") + target_compile_definitions(e3ap PRIVATE E3_ASN1_FORMAT) + target_link_libraries(e3ap PUBLIC ${CLIBE3_LIBRARIES}) +elseif(E3_ENCODING_FORMAT STREQUAL "JSON") + target_compile_definitions(e3ap PRIVATE E3_JSON_FORMAT) + pkg_check_modules(JSON_C REQUIRED json-c) + if(NOT JSON_C_FOUND) + message(FATAL_ERROR "JSON-C not found and required for JSON encoding, install it with apt-get install libjson-c-dev") + endif() + target_link_libraries(e3ap PUBLIC ${CLIBE3_LIBRARIES} ${JSON_C_LIBRARIES}) + target_include_directories(e3ap PUBLIC ${JSON_C_INCLUDE_DIRS}) +endif() diff --git a/openair2/E3AP/README.md b/openair2/E3AP/README.md new file mode 100644 index 0000000000..ed310d1504 --- /dev/null +++ b/openair2/E3AP/README.md @@ -0,0 +1,118 @@ + + +# Overview + +This document describes the E3 Agent for dApps integrated in OAI gNB, and explains how to +build, configure, and connect a dApp. + +[[_TOC_]] + +dApps can be real-time microservices co-located with the RAN node (DU or CU). Unlike xApps, +which run on a nearRT-RIC and operate at 10 ms–1 s timescales, dApps execute directly on the +RAN node and interact with user-plane data that is unavailable to RICs due to timing or +privacy constraints. This enables sub-millisecond control loops for use cases such as spectrum +sharing, positioning, and scheduling optimization. + +The E3 interface connects the gNB to co-located dApps. The `libe3` library implements both +sides of this interface — the RAN agent (this module) and the dApp client — using a unified +C++/Python API that hides transport, encoding, and protocol complexity. + +For a complete description of the dApp architecture and the E3 interface, refer to: + +- [Paper: dApp architecture and E3 interface (Computer Networks, 2025)](https://doi.org/10.1016/j.comnet.2025.111342) +- [Tutorial: deploying dApps with OAI](https://openrangym.com/tutorials/dapps-oai) +- [dApp Python library](https://pypi.org/project/dapps/) + +## 1. Prerequisites — libe3 + +The E3 Agent requires the external `libe3` library, discovered at build time via +`pkg_check_modules(CLIBE3 REQUIRED libe3)`. It is not vendored in OAI. + +```bash +git clone https://github.com/wineslab/libe3 && cd libe3 +./build_libe3 -I # install system prerequisites if not already present +./build_libe3 --install +``` + +## 2. Build OAI with the E3 Agent + +The E3 Agent is **opt-in** and gated behind the `E3_AGENT` CMake option (`OFF` by default). +The default OAI build is entirely unaffected when `E3_AGENT` is not set. + +```bash +cd cmake_targets/ +./build_oai -w SIMU --ninja --gNB --nrUE --build-e3 +``` + +To build without the E3 Agent, omit `--build-e3`. To clean and rebuild, add the `-c` flag. + +## 3. Configuration + +The agent reads an optional `E3Configuration` block from the gNB configuration file. +If the block is absent, the agent falls back to built-in defaults (`posix`/`ipc`). + +``` +E3Configuration : { + link = "zmq"; # posix | zmq + transport = "ipc"; # tcp | sctp | ipc +}; +``` + +The same parameters can be set on the command line without editing the config file: + +```bash +sudo ./nr-softmodem -O ... --E3Configuration.link zmq --E3Configuration.transport ipc +``` + +Valid link/transport combinations: + +| link | transport | +|-------|-----------| +| zmq | ipc | +| zmq | tcp | +| posix | ipc | +| posix | tcp | +| posix | sctp | + +## 4. dApp development + +dApps are written in Python using the `dapps` library: + +```bash +pip install "dapps[all]" +``` + +A dApp subclasses the `DApp` base class and implements service-model-specific encode/decode +and a control loop. The library handles the E3 connection transparently: setup handshake, +subscription, indication receive, and control send. + +At the current status, no service models have been included so far. + +For a full end-to-end example including service model usage, follow the +[tutorial on the OpenRAN Gym website](https://openrangym.com/tutorials/dapps-oai). + +## 5. Start the process + +### 5.1 Start the gNB with E3 agent enabled + +The example below uses RFsim for local testing. A 5G Core Network must already be running; +see [`doc/NR_SA_Tutorial_OAI_CN5G.md`](../../doc/NR_SA_Tutorial_OAI_CN5G.md). + +```bash +cd cmake_targets/ran_build/build +sudo ./nr-softmodem \ + -O /targets/PROJECTS/GENERIC-NR-5GC/CONF/gnb.sa.band78.fr1.106PRB.pci0.rfsim.conf \ + --rfsim \ + --E3Configuration.link zmq --E3Configuration.transport ipc +``` + +If the agent initializes correctly, the log will show `Init E3 Agent` and the setup socket +will appear at `/tmp/dapps/setup`. + +An `E3AP` log component is available for debugging: pass `--log_config.e3ap_log_level debug` +to increase verbosity. + +### 5.2 Connect a dApp + +Follow the [deployment tutorial](https://openrangym.com/tutorials/dapps-oai) for a complete +end-to-end example. diff --git a/openair2/E3AP/config/e3_config.c b/openair2/E3AP/config/e3_config.c new file mode 100644 index 0000000000..b61355419a --- /dev/null +++ b/openair2/E3AP/config/e3_config.c @@ -0,0 +1,73 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "e3_config.h" +#include "common/utils/LOG/log.h" +#include "common/config/config_paramdesc.h" +#include "common/config/config_userapi.h" + +#define E3CONFIG_SECTION "E3Configuration" + +#define simOpt PARAMFLAG_NOFREE | PARAMFLAG_CMDLINE_NOPREFIXENABLED + +/* String-to-integer mappings for link, transport, and encoding. + * Values must match the libe3 e3_config_t enum ordering. */ +// clang-format off +#define E3_LINK_OKSTRINGS {"zmq", "posix"} +#define E3_LINK_VALUES {E3_LINK_ZMQ, E3_LINK_POSIX} +#define E3_TRANSPORT_OKSTRINGS {"sctp", "tcp", "ipc"} +#define E3_TRANSPORT_VALUES {E3_TRANSPORT_SCTP, E3_TRANSPORT_TCP, E3_TRANSPORT_IPC} +#define E3_ENCODING_OKSTRINGS {"asn1", "json"} +#define E3_ENCODING_VALUES {E3_ENCODING_ASN1, E3_ENCODING_JSON} + +#define E3_LINK_IDX 0 +#define E3_TRANSPORT_IDX 1 +#define E3_ENCODING_IDX 2 +// clang-format on + +void e3_readconfig(e3_cmdline_config_t *config) +{ + /* Temporary string storage for the string→int parameters. */ + char *s_link = NULL, *s_transport = NULL, *s_encoding = NULL; + + // clang-format off + paramdef_t e3_params[] = { + /* optname helpstr paramflags value defval type numelt */ + {"link", "Link layer for E3 (zmq|posix)", simOpt, .strptr = &s_link, .defstrval = "posix", TYPE_STRING, 0}, + {"transport", "Transport layer for E3 (sctp|tcp|ipc)", simOpt, .strptr = &s_transport, .defstrval = "ipc", TYPE_STRING, 0}, + {"encoding", "Encoding format for E3 (asn1|json)", simOpt, .strptr = &s_encoding, .defstrval = "asn1", TYPE_STRING, 0}, + {"setup_port", "E3 setup port (0=libe3 default)", simOpt, .u16ptr = &config->setup_port, .defuintval = 0, TYPE_UINT16, 0}, + {"subscriber_port", "E3 subscriber port (0=libe3 default)", simOpt, .u16ptr = &config->subscriber_port, .defuintval = 0, TYPE_UINT16, 0}, + {"publisher_port", "E3 publisher port (0=libe3 default)", simOpt, .u16ptr = &config->publisher_port, .defuintval = 0, TYPE_UINT16, 0}, + }; + + checkedparam_t e3_checks[] = { + {.s3a = {config_checkstr_assign_integer, E3_LINK_OKSTRINGS, E3_LINK_VALUES, 2}}, + {.s3a = {config_checkstr_assign_integer, E3_TRANSPORT_OKSTRINGS, E3_TRANSPORT_VALUES, 3}}, + {.s3a = {config_checkstr_assign_integer, E3_ENCODING_OKSTRINGS, E3_ENCODING_VALUES, 2}}, + {.s5 = {NULL}}, + {.s5 = {NULL}}, + {.s5 = {NULL}}, + }; + // clang-format on + + config_set_checkfunctions(e3_params, e3_checks, sizeofArray(e3_params)); + + int ret = config_get(config_get_if(), e3_params, sizeofArray(e3_params), E3CONFIG_SECTION); + AssertFatal(ret >= 0, "E3Configuration: config_get failed\n"); + + config->link_layer = config_get_processedint(config_get_if(), &e3_params[E3_LINK_IDX]); + config->transport_layer = config_get_processedint(config_get_if(), &e3_params[E3_TRANSPORT_IDX]); + config->encoding = config_get_processedint(config_get_if(), &e3_params[E3_ENCODING_IDX]); + /* setup_port, subscriber_port, publisher_port written directly by config_get via u16ptr */ + + LOG_I(E3AP, + "E3 configuration: link=%d transport=%d encoding=%d setup_port=%u subscriber_port=%u publisher_port=%u\n", + config->link_layer, + config->transport_layer, + config->encoding, + config->setup_port, + config->subscriber_port, + config->publisher_port); +} diff --git a/openair2/E3AP/config/e3_config.h b/openair2/E3AP/config/e3_config.h new file mode 100644 index 0000000000..729cbd1f7e --- /dev/null +++ b/openair2/E3AP/config/e3_config.h @@ -0,0 +1,38 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#ifndef E3_CONFIG_H +#define E3_CONFIG_H + +#include + +/* link_layer values (match libe3 e3_config_t; -1 = libe3 default) */ +#define E3_LINK_ZMQ 0 +#define E3_LINK_POSIX 1 + +/* transport_layer values (match libe3 e3_config_t; -1 = libe3 default) */ +#define E3_TRANSPORT_SCTP 0 +#define E3_TRANSPORT_TCP 1 +#define E3_TRANSPORT_IPC 2 + +/* encoding values (match libe3 e3_config_t; -1 = libe3 default) */ +#define E3_ENCODING_ASN1 0 +#define E3_ENCODING_JSON 1 + +typedef struct { + int link_layer; /* E3_LINK_{ZMQ,POSIX}; -1 = libe3 default */ + int transport_layer; /* E3_TRANSPORT_{SCTP,TCP,IPC}; -1 = libe3 default */ + int encoding; /* E3_ENCODING_{ASN1,JSON}; -1 = libe3 default */ + uint16_t setup_port; /* 0 = libe3 default (9990) */ + uint16_t subscriber_port; /* 0 = libe3 default */ + uint16_t publisher_port; /* 0 = libe3 default */ +} e3_cmdline_config_t; + +/** + * @brief Read E3 configuration from the OAI config file + * @param config Pointer to configuration structure to fill + */ +void e3_readconfig(e3_cmdline_config_t *config); + +#endif // E3_CONFIG_H diff --git a/openair2/E3AP/e3_agent.c b/openair2/E3AP/e3_agent.c new file mode 100644 index 0000000000..c18bcb8a89 --- /dev/null +++ b/openair2/E3AP/e3_agent.c @@ -0,0 +1,206 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#include "e3_agent.h" +#include "config/e3_config.h" + +// TODO replace pthreads with itti or use a faster way +// #include "intertask_interface.h" +// #include "create_tasks.h" +#include +#include +#include + +#include + +#include "common/utils/system.h" +#include "common/ran_context.h" +#include "common/utils/LOG/log.h" +#include "openair2/GNB_APP/gnb_paramdef.h" + +e3_agent_global_t e3 = {0}; + +static void e2_e3_bridge(uint32_t dapp_id, uint32_t ran_function_id, const uint8_t *report_data, size_t report_size) +{ + LOG_D(E3AP, "Received dApp report for RAN function %u from dApp %u (%zu bytes)\n", ran_function_id, dapp_id, report_size); +#ifdef E2_AGENT + if (!report_data && report_size > 0) { + LOG_E(E3AP, "Invalid dApp report payload: report_data is NULL while report_size=%zu\n", report_size); + return; + } + generate_e2_indication_from_e3_dapp_report(ran_function_id, dapp_id, report_size, report_data); +#else + (void)report_data; +#endif +} + +void on_dapp_status_changed(void) +{ + LOG_I(E3AP, "dApp status changed, triggering RIC Service Update\n"); +#ifdef E2_AGENT + notify_dapp_status_changed(); +#endif +} + +int e3_init() +{ + LOG_D(E3AP, "Read configuration\n"); + e3_cmdline_config_t *e3_cmdline_configs = (e3_cmdline_config_t *)calloc(1, sizeof(e3_cmdline_config_t)); + if (!e3_cmdline_configs) { + LOG_E(E3AP, "Failed to allocate E3 cmdline config\n"); + return -1; + } + e3_readconfig(e3_cmdline_configs); + + /* Read gNB name to use as E3 agent identifier */ + char *gnb_name = NULL; + paramdef_t name_param[] = {{GNB_CONFIG_STRING_GNB_NAME, NULL, 0, .strptr = &gnb_name, .defstrval = "OAI gNB", TYPE_STRING, 0}}; + config_get(config_get_if(), name_param, 1, GNB_CONFIG_STRING_GNB_LIST ".[0]"); + + e3_config_t config = {0}; + config.ran_identifier = gnb_name ? gnb_name : "OAI gNB"; + config.log_level = g_log->log_component[E3AP].level; + config.link_layer = e3_cmdline_configs->link_layer; + config.transport_layer = e3_cmdline_configs->transport_layer; + config.encoding = e3_cmdline_configs->encoding; + config.setup_port = e3_cmdline_configs->setup_port; + config.subscriber_port = e3_cmdline_configs->subscriber_port; + config.publisher_port = e3_cmdline_configs->publisher_port; + + e3.agent = e3_agent_create_with_config(&config); + free(e3_cmdline_configs); + e3_cmdline_configs = NULL; + if (!e3.agent) { + LOG_E(E3AP, "Failed to create E3Agent with config\n"); + return -1; + } + + // Initialize agent + e3_error_t err = e3_agent_init(e3.agent); + if (err != 0) { + LOG_E(E3AP, "Failed to initialize E3Agent (err=%d)\n", err); + e3_agent_destroy(e3.agent); + e3.agent = NULL; + return -1; + } + + // Start agent + err = e3_agent_start(e3.agent); + if (err != 0) { + LOG_E(E3AP, "Failed to start E3Agent (err=%d)\n", err); + e3_agent_destroy(e3.agent); + e3.agent = NULL; + return -1; + } + + err = e3_agent_set_dapp_report_handler(e3.agent, e2_e3_bridge); + if (err != 0) { + LOG_E(E3AP, "Failed to set dApp report handler (err=%d: %s)\n", err, e3_error_to_string(err)); + e3_agent_destroy(e3.agent); + e3.agent = NULL; + return -1; + } + + err = e3_agent_set_dapp_status_changed_handler(e3.agent, on_dapp_status_changed); + if (err != 0) { + LOG_E(E3AP, "Failed to set dApp status changed handler (err=%d: %s)\n", err, e3_error_to_string(err)); + e3_agent_destroy(e3.agent); + e3.agent = NULL; + return -1; + } + + // No concrete service models are registered by the framework itself; service + // models (e.g. spectrum sensing) register themselves in their own modules. + return 0; +} + +int e3_destroy() +{ + // Stop and destroy the E3Agent if it exists + if (e3.agent) { + e3_agent_stop(e3.agent); + e3_agent_destroy(e3.agent); + e3.agent = NULL; + } + + return 0; +} + +int e3_send_xapp_control(uint32_t dapp_id, uint32_t ran_function_id, const uint8_t *data, size_t len) +{ + if (!e3.agent) { + LOG_E(E3AP, "E3 agent not initialized: cannot send xApp control\n"); + return -1; + } + + if (data == NULL && len > 0) { + LOG_E(E3AP, "data is not initialized, but len > 0\n"); + return -1; + } + + e3_error_t err = e3_agent_send_xapp_control(e3.agent, dapp_id, ran_function_id, data, len); + if (err != E3_SUCCESS) { + LOG_E(E3AP, "Failed to send xApp control to dApp %u for RAN function %u (err=%d)\n", dapp_id, ran_function_id, err); + return -1; + } + return 0; +} + +e3_dapp_subscription_map_t e3_get_dapp_subscription_map(void) +{ + e3_dapp_subscription_map_t map = {0}; + + if (!e3.agent) { + LOG_W(E3AP, "E3 agent not initialized: cannot query dApp subscriptions\n"); + return map; + } + + size_t num_dapps = 0; + uint32_t *dapp_ids = e3_agent_get_registered_dapps(e3.agent, &num_dapps); + if (!dapp_ids || num_dapps == 0) { + e3_agent_free_uint32_array(dapp_ids); + return map; + } + + map.dapps = calloc(num_dapps, sizeof(e3_dapp_info_t)); + if (!map.dapps) { + LOG_E(E3AP, "Failed to allocate dApp subscription map\n"); + e3_agent_free_uint32_array(dapp_ids); + return map; + } + map.num_dapps = num_dapps; + + for (size_t i = 0; i < num_dapps; i++) { + map.dapps[i].dapp_id = dapp_ids[i]; + + size_t num_subs = 0; + uint32_t *subs = e3_agent_get_dapp_subscriptions(e3.agent, dapp_ids[i], &num_subs); + + if (subs && num_subs > 0) { + map.dapps[i].e3_ran_func_ids = subs; // transfer ownership from libe3 malloc + map.dapps[i].num_e3_ran_funcs = num_subs; + } else { + map.dapps[i].e3_ran_func_ids = NULL; + map.dapps[i].num_e3_ran_funcs = 0; + e3_agent_free_uint32_array(subs); + } + } + + e3_agent_free_uint32_array(dapp_ids); + return map; +} + +void e3_free_dapp_subscription_map(e3_dapp_subscription_map_t *map) +{ + if (!map || !map->dapps) + return; + + for (size_t i = 0; i < map->num_dapps; i++) { + e3_agent_free_uint32_array(map->dapps[i].e3_ran_func_ids); + } + free(map->dapps); + + map->dapps = NULL; + map->num_dapps = 0; +} diff --git a/openair2/E3AP/e3_agent.h b/openair2/E3AP/e3_agent.h new file mode 100644 index 0000000000..3b1253467c --- /dev/null +++ b/openair2/E3AP/e3_agent.h @@ -0,0 +1,60 @@ +/* + * SPDX-License-Identifier: LicenseRef-CSSL-1.0 + */ + +#ifndef E3_AGENT_H +#define E3_AGENT_H + +#include +#include +#include + +#include + +#include + +#include "config/e3_config.h" + +#ifdef E2_AGENT +#include "ran_func_dapp_extern.h" +#endif + +typedef struct { + e3_agent_handle_t *agent; + e3_service_model_handle_t **service_models; + size_t num_service_models; +} e3_agent_global_t; + +typedef struct { + uint32_t dapp_id; + uint32_t *e3_ran_func_ids; + size_t num_e3_ran_funcs; +} e3_dapp_info_t; + +typedef struct { + e3_dapp_info_t *dapps; + size_t num_dapps; +} e3_dapp_subscription_map_t; + +extern e3_agent_global_t e3; + +int e3_init(); +int e3_destroy(); + +int e3_send_xapp_control(uint32_t dapp_id, uint32_t ran_function_id, const uint8_t *data, size_t len); + +/** + * @brief Get all connected dApps and their RAN function subscriptions. + * + * Caller must free the result with e3_free_dapp_subscription_map(). + * + * @return populated map on success, {NULL, 0} on error or if no dApps + */ +e3_dapp_subscription_map_t e3_get_dapp_subscription_map(void); + +/** + * @brief Free a subscription map returned by e3_get_dapp_subscription_map(). + */ +void e3_free_dapp_subscription_map(e3_dapp_subscription_map_t *map); + +#endif // E3_AGENT_H