Merge remote-tracking branch 'Thecave3/e3-agent-framework' into integration_2026_w27

Implementation of the E3 Agent for dApps and E3AP (no service models) (#204)

These changes introduce an E3 Agent for dApps in the OAI codebase. dApps
are real-time microservices designed to operate within the O-RAN
ecosystem, enabling sub-10 ms Artificial Intelligence (AI) routines in
the Radio Access Network (RAN).

The E3 Agent enables spectrum sensing, frequency-domain signal logging,
and dynamic PRB (Physical Resource Block) policies through IPC/ZMQ
communication with dApps or xApps.

The dApps are designed for co-location with the CU/DU, where it can
interact directly with user-plane data to enable different network
management scenarios such as optimizing network slicing, scheduling, and
resource management.  A python library for the creation of the dApps is
available here [1].

Additional information about dApps:

- Paper on the dApp architecture [2]
- dApp framework presentation [3]
- Tutorial on dApp deployment on OAI [4]

As agreed in the original MR [5], this PR includes only the agent lifecycle,
configuration parsing, build plumbing, and the nr-softmodem init/destroy
hook.

It registers zero service models, so it builds and runs as an idle
agent. Concrete service models (e.g. spectrum sensing) and the PHY/MAC
hooks that drive them follow in later PRs.

Opt-in and dependencies

- Gated behind the E3_AGENT CMake option (OFF by default) / the
  --build-e3 build flag.  The default build is unaffected — e3ap is only
  compiled and linked into nr-softmodem when E3_AGENT=ON.

- When enabled, requires the external libe3 library [6] (vendor-neutral
  E3AP C++ lib), discovered via pkg_check_modules(CLIBE3 REQUIRED
  libe3). It is not vendored.

  Install libe3 (Debian/Ubuntu):

      git clone https://github.com/wineslab/libe3 && cd libe3
      ./build_libe3 -I      # installs deps: build-essential cmake pkg-config libzmq3-dev asn1c nlohmann-json3-dev libsctp-dev ...
      ./build_libe3 --install      # release build + installation

Build & test

Tested in RFsim: the gNB initializes the E3 agent, opens its setup
socket (/tmp/dapps/setup), registers zero service models, and runs
without crashing.  Configuration

The agent reads an optional E3Configuration section from the gNB config
file (parsed only when E3_AGENT=ON). If absent, it falls back to
built-in defaults (posix/ipc).

    E3Configuration : {
        link = "zmq";        # posix | zmq
        transport = "ipc";   # tcp | sctp | ipc
    };

The same parameters can be overridden on the command line (no
config-file edit required):

    sudo ./nr-softmodem -O <gnb.conf> ... --E3Configuration.link zmq --E3Configuration.transport ipc

Validated link/transport combinations: (zmq,ipc) (zmq,tcp) (posix,tcp)
(posix,sctp) (posix,ipc).

Notes

- I have added a new log component E3AP specific for the ops performed
  by the E3 Agent.
- This PR ships no gNB config file; the E3Configuration block / CLI
  override above is the documented way to enable the agent. Example
  configs follow in the next PRs.

[1] https://pypi.org/project/dapps/
[2] https://arxiv.org/pdf/2501.16502
[3] https://openrangym.com/o-ran-frameworks/dapps
[4] https://openrangym.com/tutorials/dapps-oai
[5] https://gitlab.eurecom.fr/oai/openairinterface5g/-/merge_requests/3361
[6] https://github.com/wineslab/libe3

Reviewed-by: Teodora Vladić <teodora.vladic@openairinterface.org>
Reviewed-by: Sagar Arora <sagar.arora@openairinterface.org>
Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-07-02 18:35:23 +02:00
12 changed files with 599 additions and 0 deletions

View File

@@ -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 #
##################################################
@@ -1831,6 +1837,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

View File

@@ -43,6 +43,8 @@ Options:
Sets build directory (will be <oai-root>/cmake_targets/<build-dir>/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"

View File

@@ -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,

View File

@@ -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

View File

@@ -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 <common/utils/assertions.h>
#include <openair2/GNB_APP/gnb_app.h>
@@ -520,6 +525,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
@@ -726,6 +738,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");

View File

@@ -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)

View File

@@ -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()

118
openair2/E3AP/README.md Normal file
View File

@@ -0,0 +1,118 @@
<!-- SPDX-License-Identifier: CC-BY-4.0 -->
# 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 ms1 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 <gnb.conf> ... --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 <path>/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.

View File

@@ -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);
}

View File

@@ -0,0 +1,38 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef E3_CONFIG_H
#define E3_CONFIG_H
#include <stdint.h>
/* 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

206
openair2/E3AP/e3_agent.c Normal file
View File

@@ -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 <pthread.h>
#include <errno.h>
#include <time.h>
#include <libe3/c_api.h>
#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;
}

60
openair2/E3AP/e3_agent.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef E3_AGENT_H
#define E3_AGENT_H
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <libe3/c_api.h>
#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