Commit 69a017f5 authored by Robert Schmidt's avatar Robert Schmidt

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/libe3Reviewed-by: default avatarTeodora Vladić <teodora.vladic@openairinterface.org>
Reviewed-by: default avatarSagar Arora <sagar.arora@openairinterface.org>
Reviewed-by: default avatarRobert Schmidt <robert.schmidt@openairinterface.org>
parents 465b57d8 602d729d
...@@ -383,6 +383,12 @@ endif() ...@@ -383,6 +383,12 @@ endif()
add_boolean_option(ENABLE_IMSCOPE OFF "Enable phy scope based on imgui" OFF) 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) 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 # # ASN.1 grammar C code generation & dependencies #
################################################## ##################################################
...@@ -1831,6 +1837,11 @@ if(E2_AGENT) ...@@ -1831,6 +1837,11 @@ if(E2_AGENT)
target_compile_definitions(nr-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT) target_compile_definitions(nr-softmodem PRIVATE ${E2AP_VERSION} ${KPM_VERSION} E2_AGENT)
endif() 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 # force the generation of ASN.1 so that we don't need to wait during the build
target_link_libraries(nr-softmodem PRIVATE target_link_libraries(nr-softmodem PRIVATE
......
...@@ -43,6 +43,8 @@ Options: ...@@ -43,6 +43,8 @@ Options:
Sets build directory (will be <oai-root>/cmake_targets/<build-dir>/build) Sets build directory (will be <oai-root>/cmake_targets/<build-dir>/build)
--build-e2 --build-e2
Enable the the E2 Agent Enable the the E2 Agent
--build-e3
Enable the the E3 Agent
-I | --install-external-packages -I | --install-external-packages
Installs required packages such as LibXML, asn1.1 compiler, ... Installs required packages such as LibXML, asn1.1 compiler, ...
This option will require root password This option will require root password
...@@ -143,6 +145,10 @@ function main() { ...@@ -143,6 +145,10 @@ function main() {
CMAKE_CMD="$CMAKE_CMD -DE2_AGENT=ON" CMAKE_CMD="$CMAKE_CMD -DE2_AGENT=ON"
shift shift
;; ;;
--build-e3 )
CMAKE_CMD="$CMAKE_CMD -DE3_AGENT=ON"
shift
;;
-I | --install-external-packages) -I | --install-external-packages)
INSTALL_EXTERNAL=1 INSTALL_EXTERNAL=1
echo_info "Will install external packages" echo_info "Will install external packages"
......
...@@ -155,6 +155,7 @@ static const char *const flag_name[] = {FOREACH_FLAG(FLAG_TEXT) ""}; ...@@ -155,6 +155,7 @@ static const char *const flag_name[] = {FOREACH_FLAG(FLAG_TEXT) ""};
COMP_DEF(NFAPI_PNF, log) \ COMP_DEF(NFAPI_PNF, log) \
COMP_DEF(ITTI, log) \ COMP_DEF(ITTI, log) \
COMP_DEF(UTIL, log) \ COMP_DEF(UTIL, log) \
COMP_DEF(E3AP, log) \
COMP_DEF(MAX_LOG_PREDEF_COMPONENTS, ) COMP_DEF(MAX_LOG_PREDEF_COMPONENTS, )
#define COMP_ENUM(comp, file_extension) comp, #define COMP_ENUM(comp, file_extension) comp,
......
...@@ -434,6 +434,27 @@ ID = LEGACY_MAC_TRACE ...@@ -434,6 +434,27 @@ ID = LEGACY_MAC_TRACE
GROUP = ALL:LEGACY_MAC:LEGACY_GROUP_TRACE:LEGACY GROUP = ALL:LEGACY_MAC:LEGACY_GROUP_TRACE:LEGACY
FORMAT = string,log 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 ID = LEGACY_NR_MAC_INFO
DESC = NR_MAC legacy logs - info level DESC = NR_MAC legacy logs - info level
GROUP = ALL:LEGACY_NR_MAC:LEGACY_GROUP_INFO:LEGACY GROUP = ALL:LEGACY_NR_MAC:LEGACY_GROUP_INFO:LEGACY
......
...@@ -19,6 +19,11 @@ unsigned short config_frames[4] = {2,9,11,13}; ...@@ -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/flexric/src/agent/e2_agent_api.h"
#include "openair2/E2AP/RAN_FUNCTION/init_ran_func.h" #include "openair2/E2AP/RAN_FUNCTION/init_ran_func.h"
#endif #endif
#ifdef E3_AGENT
#include "openair2/E3AP/e3_agent.h"
#endif
#include "nr-softmodem.h" #include "nr-softmodem.h"
#include <common/utils/assertions.h> #include <common/utils/assertions.h>
#include <openair2/GNB_APP/gnb_app.h> #include <openair2/GNB_APP/gnb_app.h>
...@@ -520,6 +525,13 @@ int main( int argc, char **argv ) { ...@@ -520,6 +525,13 @@ int main( int argc, char **argv ) {
softmodem_verify_mode(get_softmodem_params()); 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 #if T_TRACER
T_Config_Init(); T_Config_Init();
#endif #endif
...@@ -726,6 +738,11 @@ int main( int argc, char **argv ) { ...@@ -726,6 +738,11 @@ int main( int argc, char **argv ) {
time_manager_finish(); time_manager_finish();
#ifdef E3_AGENT
printf("Destroy E3 Agent\n");
e3_destroy();
#endif // E3_AGENT
free(pckg); free(pckg);
logClean(); logClean();
printf("Bye.\n"); printf("Bye.\n");
......
...@@ -5,6 +5,10 @@ add_subdirectory(E1AP) ...@@ -5,6 +5,10 @@ add_subdirectory(E1AP)
if(E2_AGENT) if(E2_AGENT)
add_subdirectory(E2AP) add_subdirectory(E2AP)
endif() endif()
if(E3_AGENT)
message(STATUS "Add E3 Agent capabilities")
add_subdirectory(E3AP)
endif()
add_subdirectory(F1AP) add_subdirectory(F1AP)
add_subdirectory(LAYER2) add_subdirectory(LAYER2)
add_subdirectory(M2AP) add_subdirectory(M2AP)
......
# 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()
<!-- 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 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 <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.
/*
* 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);
}
/*
* 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
/*
* 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;
}
/*
* 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment