Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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 #
##################################################
Expand Down Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions cmake_targets/build_oai
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions common/utils/LOG/log.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
21 changes: 21 additions & 0 deletions common/utils/T/T_messages.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
17 changes: 17 additions & 0 deletions executables/nr-softmodem.c
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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");
Expand Down
4 changes: 4 additions & 0 deletions openair2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Comment thread
rorsc marked this conversation as resolved.
endif()
add_subdirectory(F1AP)
add_subdirectory(LAYER2)
add_subdirectory(M2AP)
Expand Down
44 changes: 44 additions & 0 deletions openair2/E3AP/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# SPDX-License-Identifier: LicenseRef-CSSL-1.0

find_package(PkgConfig REQUIRED)
Comment thread
arora-sagar marked this conversation as resolved.
pkg_check_modules(CLIBE3 REQUIRED libe3)
Comment thread
teodora-vladic marked this conversation as resolved.

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)
Comment thread
rorsc marked this conversation as resolved.

# 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 changes: 118 additions & 0 deletions openair2/E3AP/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 73 additions & 0 deletions openair2/E3AP/config/e3_config.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/

#include "e3_config.h"
Comment thread
arora-sagar marked this conversation as resolved.
Comment thread
teodora-vladic marked this conversation as resolved.
#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);
}
Loading
Loading