mirror of
https://gitlab.eurecom.fr/oai/openairinterface5g.git
synced 2026-07-13 04:30:28 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
44
openair2/E3AP/CMakeLists.txt
Normal file
44
openair2/E3AP/CMakeLists.txt
Normal 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
118
openair2/E3AP/README.md
Normal 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 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.
|
||||
73
openair2/E3AP/config/e3_config.c
Normal file
73
openair2/E3AP/config/e3_config.c
Normal 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);
|
||||
}
|
||||
38
openair2/E3AP/config/e3_config.h
Normal file
38
openair2/E3AP/config/e3_config.h
Normal 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
206
openair2/E3AP/e3_agent.c
Normal 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
60
openair2/E3AP/e3_agent.h
Normal 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
|
||||
Reference in New Issue
Block a user