Skip to content
Open
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
21 changes: 12 additions & 9 deletions .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,12 @@ jobs:
- name: Run integration tests
run: cd build && ctest -L integration --output-on-failure

dual-encoding:
# Regression guard for issue #26: ASN.1 and JSON must be buildable together
# (the encoder is selected at runtime via E3Config.encoding).
name: Dual Encoding (ASN.1 + JSON)
all-encodings:
# Regression guard: ASN.1, JSON, and Protocol Buffers must all be buildable
# together in a single libe3 (the encoder is selected at runtime via
# E3Config.encoding). Builds one library with every encoder compiled in and
# runs the full test suite against it.
name: All Encodings (ASN.1 + JSON + Protobuf)
runs-on: ubuntu-latest
needs: test
steps:
Expand All @@ -76,18 +78,19 @@ jobs:
chmod +x ./build_libe3 || true
./build_libe3 -I || true

- name: Configure with both encoders enabled
- name: Configure with all encoders enabled
run: |
cmake -S . -B build-both \
cmake -S . -B build-all \
-DCMAKE_BUILD_TYPE=Release \
-DLIBE3_ENABLE_ASN1=ON \
-DLIBE3_ENABLE_JSON=ON
-DLIBE3_ENABLE_JSON=ON \
-DLIBE3_ENABLE_PROTOBUF=ON

- name: Build
run: cmake --build build-both -j $(nproc)
run: cmake --build build-all -j $(nproc)

- name: Run tests
run: cd build-both && ctest --output-on-failure
run: cd build-all && ctest --output-on-failure

swig:
name: SWIG Python Bindings
Expand Down
64 changes: 54 additions & 10 deletions .github/workflows/tag-and-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,37 @@ jobs:
run: |
chmod +x ./scripts/create_deb.sh || true

- name: Run packaging script for amd64
- name: Build and package all encoding variants for amd64
run: |
./scripts/create_deb.sh amd64

- name: Upload amd64 .deb artifact
set -e
build_variant() {
local name="$1" dir="$2"; shift 2
cmake -S . -B "$dir" \
-DCMAKE_BUILD_TYPE=Release \
-DLIBE3_BUILD_TESTS=OFF \
-DLIBE3_BUILD_EXAMPLES=OFF \
"$@"
cmake --build "$dir" -j "$(nproc)"
if [ -n "$name" ]; then
./scripts/create_deb.sh --use-existing-build --build-dir "$dir" --variant "$name" amd64
else
./scripts/create_deb.sh --use-existing-build --build-dir "$dir" amd64
fi
}
# Default (all encodings) keeps the primary "libe3" package name.
build_variant "" build-all -DLIBE3_ENABLE_ASN1=ON -DLIBE3_ENABLE_JSON=ON -DLIBE3_ENABLE_PROTOBUF=ON
build_variant "asn1" build-asn1 -DLIBE3_ENABLE_ASN1=ON -DLIBE3_ENABLE_JSON=OFF -DLIBE3_ENABLE_PROTOBUF=OFF
build_variant "json" build-json -DLIBE3_ENABLE_ASN1=OFF -DLIBE3_ENABLE_JSON=ON -DLIBE3_ENABLE_PROTOBUF=OFF
build_variant "protobuf" build-protobuf -DLIBE3_ENABLE_ASN1=OFF -DLIBE3_ENABLE_JSON=OFF -DLIBE3_ENABLE_PROTOBUF=ON
mkdir -p dist
cp build-*/*.deb dist/
ls -l dist/

- name: Upload amd64 .deb artifacts
uses: actions/upload-artifact@v4
with:
name: libe3-deb-amd64
path: build/libe3_${{ needs.create-tag.outputs.version }}_amd64.deb
path: dist/*.deb

build-deb-arm64:
name: Build Debian package (arm64)
Expand All @@ -98,15 +120,37 @@ jobs:
run: |
chmod +x ./scripts/create_deb.sh || true

- name: Run packaging script for arm64
- name: Build and package all encoding variants for arm64
run: |
./scripts/create_deb.sh arm64

- name: Upload arm64 .deb artifact
set -e
build_variant() {
local name="$1" dir="$2"; shift 2
cmake -S . -B "$dir" \
-DCMAKE_BUILD_TYPE=Release \
-DLIBE3_BUILD_TESTS=OFF \
-DLIBE3_BUILD_EXAMPLES=OFF \
"$@"
cmake --build "$dir" -j "$(nproc)"
if [ -n "$name" ]; then
./scripts/create_deb.sh --use-existing-build --build-dir "$dir" --variant "$name" arm64
else
./scripts/create_deb.sh --use-existing-build --build-dir "$dir" arm64
fi
}
# Default (all encodings) keeps the primary "libe3" package name.
build_variant "" build-all -DLIBE3_ENABLE_ASN1=ON -DLIBE3_ENABLE_JSON=ON -DLIBE3_ENABLE_PROTOBUF=ON
build_variant "asn1" build-asn1 -DLIBE3_ENABLE_ASN1=ON -DLIBE3_ENABLE_JSON=OFF -DLIBE3_ENABLE_PROTOBUF=OFF
build_variant "json" build-json -DLIBE3_ENABLE_ASN1=OFF -DLIBE3_ENABLE_JSON=ON -DLIBE3_ENABLE_PROTOBUF=OFF
build_variant "protobuf" build-protobuf -DLIBE3_ENABLE_ASN1=OFF -DLIBE3_ENABLE_JSON=OFF -DLIBE3_ENABLE_PROTOBUF=ON
mkdir -p dist
cp build-*/*.deb dist/
ls -l dist/

- name: Upload arm64 .deb artifacts
uses: actions/upload-artifact@v4
with:
name: libe3-deb-arm64
path: build/libe3_${{ needs.create-tag.outputs.version }}_arm64.deb
path: dist/*.deb

build-docs:
name: Build API Documentation
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Build directories
build/
build-*/
cmake-build-*/
out/
dist/

# Compiled objects
*.o
Expand Down
4 changes: 2 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ ctest -LE integration # only unit tests
PYTHONPATH=build/swig python3 tests/test_swig_smoke.py # SWIG bindings smoke test
```

**Build options** live in `cmake/libe3Options.cmake`: `LIBE3_BUILD_TESTS` (ON), `LIBE3_BUILD_EXAMPLES` (ON), `LIBE3_BUILD_INTEGRATION_TESTS` (OFF), `LIBE3_ENABLE_SWIG` (OFF), `LIBE3_ENABLE_ZMQ` (ON), `LIBE3_ENABLE_ASN1` (ON), `LIBE3_ENABLE_JSON` (OFF), `LIBE3_ENABLE_ASAN`/`LIBE3_ENABLE_TSAN` (OFF), `LIBE3_BUILD_DOCS` (OFF). **At least one of `LIBE3_ENABLE_ASN1` / `LIBE3_ENABLE_JSON` must be ON**; both can be built together and the encoder is chosen at runtime.
**Build options** live in `cmake/libe3Options.cmake`: `LIBE3_BUILD_TESTS` (ON), `LIBE3_BUILD_EXAMPLES` (ON), `LIBE3_BUILD_INTEGRATION_TESTS` (OFF), `LIBE3_ENABLE_SWIG` (OFF), `LIBE3_ENABLE_ZMQ` (ON), `LIBE3_ENABLE_ASN1` (ON), `LIBE3_ENABLE_JSON` (OFF), `LIBE3_ENABLE_PROTOBUF` (OFF), `LIBE3_ENABLE_ASAN`/`LIBE3_ENABLE_TSAN` (OFF), `LIBE3_BUILD_DOCS` (OFF). **At least one of `LIBE3_ENABLE_ASN1` / `LIBE3_ENABLE_JSON` / `LIBE3_ENABLE_PROTOBUF` must be ON**; any combination can be built together and the encoder is chosen at runtime. `build_libe3` has a dedicated `--enable-protobuf` flag; JSON and SWIG use `--cmake-opt`.

## Architecture (big picture)

Expand All @@ -64,7 +64,7 @@ The design has one façade over three pluggable seams, plus a real-time data pat

**Real-time data path.** Producers never touch the network directly. Lock-free MPMC ring buffers (`include/libe3/mpmc_queue.hpp`, wrapped by `lockfree_queue.hpp`) decouple work from dedicated I/O threads that `E3Interface` spawns: setup, inbound, and outbound, plus RAN-only threads that poll Service Models for telemetry and drain dApp reports off the receive path. Optional CPU affinity and niceness (Linux) are set from `E3Config` to cut scheduling jitter in sub-millisecond loops.

**State & types.** `SubscriptionManager` (RAN side) and `DAppSubscriptionState` (dApp side) track subscriptions and assigned IDs. The 11 E3AP message types are modeled as a `std::variant`-based `Pdu` in `include/libe3/types.hpp`. The wire protocol's source of truth is the ASN.1 grammar `messages/asn1/V1/e3ap-1.0.0.asn1`, from which `asn1c` generates C encode/decode code at build time (the `asn1_e3ap` target).
**State & types.** `SubscriptionManager` (RAN side) and `DAppSubscriptionState` (dApp side) track subscriptions and assigned IDs. The 11 E3AP message types are modeled as a `std::variant`-based `Pdu` in `include/libe3/types.hpp`. Each wire encoding has its own grammar under `messages/`, generated at build time into a dedicated library: ASN.1 in `messages/asn1/V1/e3ap-1.0.0.asn1` (`asn1c` -> `asn1_e3ap`, C) and Protocol Buffers in `messages/proto/V1/e3ap-1.0.0.proto` (`protoc` -> `pb_e3ap`, C++). Keep these grammars in sync with the `Pdu` structs when adding fields.

**Bindings & examples.** A C API lives in `include/libe3/c_api.h`; Python bindings via SWIG (`swig/libe3.i` → the `libe3py` module). `examples/simple_agent.cpp` (RAN) and `examples/simple_dapp.cpp` (dApp), with the reference Simple SM in `examples/sm_simple/`, are the best runnable illustration of the two roles (they require ASN.1).

Expand Down
21 changes: 10 additions & 11 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,23 @@ include(libe3Version)
# ============================================================================
# Encoder selection
# ============================================================================
# ASN.1 and JSON can be enabled together: the encoder factory
# (src/encoder/encoder_factory.cpp) creates the right encoder per
# EncodingFormat at runtime, so a single build can serve either encoding
# ASN.1, JSON, and Protocol Buffers can be enabled together: the encoder
# factory (src/encoder/encoder_factory.cpp) creates the right encoder per
# EncodingFormat at runtime, so a single build can serve any enabled encoding
# (selected by E3Config.encoding). At least one must be enabled.
if(NOT LIBE3_ENABLE_ASN1 AND NOT LIBE3_ENABLE_JSON)
message(FATAL_ERROR "Neither LIBE3_ENABLE_ASN1 nor LIBE3_ENABLE_JSON is enabled. Enable at least one encoding.")
endif()
if(LIBE3_ENABLE_ASN1 AND LIBE3_ENABLE_JSON)
message(STATUS "Both ASN.1 and JSON encoders enabled; encoding is selected at runtime via E3Config.encoding.")
if(NOT LIBE3_ENABLE_ASN1 AND NOT LIBE3_ENABLE_JSON AND NOT LIBE3_ENABLE_PROTOBUF)
message(FATAL_ERROR "No encoding enabled. Enable at least one of LIBE3_ENABLE_ASN1, LIBE3_ENABLE_JSON, or LIBE3_ENABLE_PROTOBUF.")
endif()

# ============================================================================
# ASN.1 Messages Library
# Generated Message Libraries (ASN.1 and/or Protocol Buffers)
# ============================================================================
if(LIBE3_ENABLE_ASN1)
# The messages/ subdirectory generates the asn1_e3ap and/or pb_e3ap libraries
# from the E3AP grammars, gated internally on the enabled encodings.
if(LIBE3_ENABLE_ASN1 OR LIBE3_ENABLE_PROTOBUF)
add_subdirectory(messages)
else()
message(STATUS "ASN.1 encoding support disabled by configuration")
message(STATUS "ASN.1 and Protobuf encoding support disabled by configuration")
endif()

# ============================================================================
Expand Down
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
- **Both Roles**: Single `E3Agent` class acts as either **RAN** (server, binds sockets) or **dApp** (client, connects to a remote RAN), switched by `E3Config.role`
- **Multi-Peer dApps**: One dApp process can hold N `E3Agent` instances connecting to N different RAN agents (e.g. a DU-HIGH and a DU-LOW)
- **Multiple Transports**: Support for ZeroMQ and POSIX sockets (TCP, SCTP, Unix Domain)
- **Multiple Encodings**: ASN.1 APER (primary) and JSON encoders
- **Multiple Encodings**: ASN.1 APER (primary), JSON, and Protocol Buffers encoders
- **Service Model Extensions**: Easy-to-implement SM interface for custom functionality
- **Python Bindings**: Optional SWIG bindings (`LIBE3_ENABLE_SWIG=ON`) so the same C++ library can back Python dApp clients
- **Thread-Safe**: Proper synchronization for concurrent dApp operations
Expand All @@ -36,7 +36,7 @@
│ E3Agent │◄──────────────────────────►│ E3Agent │
│ │ ZMQ / POSIX │ │
│ register_sm(...) │ IPC · TCP · SCTP │ set_indication_ │
│ send_indication │ ASN.1 APER · JSON │ handler(fn) │
│ send_indication │ ASN.1 · JSON · Protobuf │ handler(fn) │
│ send_xapp_ctrl │ │ subscribe(rfid, …) │
└───────────────────┘ │ send_control(…) │
│ send_report(…) │
Expand Down Expand Up @@ -77,6 +77,7 @@
- pthreads
- `asn1c` — ASN.1 APER encoder, required by `messages/`
- `nlohmann-json3-dev` — Header-only JSON library; CMake expects the `nlohmann_json` target
- `protobuf-compiler` + `libprotobuf-dev` — `protoc` and the Protocol Buffers runtime (only when `LIBE3_ENABLE_PROTOBUF=ON`)
- `libzmq3-dev` for ZMQ transport
- `libsctp-dev` — SCTP development headers/libraries for POSIX/SCTP transport

Expand All @@ -90,7 +91,7 @@ Install all required packages using the project's installer (recommended) or man

# Manual (Debian/Ubuntu)
sudo apt update
sudo apt install -y build-essential cmake pkg-config libzmq3-dev ninja-build git asn1c nlohmann-json3-dev libsctp-dev dpkg-dev debhelper fakeroot
sudo apt install -y build-essential cmake pkg-config libzmq3-dev ninja-build git asn1c nlohmann-json3-dev protobuf-compiler libprotobuf-dev libsctp-dev dpkg-dev debhelper fakeroot
```

The packaging tools (`dpkg-dev`, `debhelper`, `fakeroot`) are only needed by `scripts/create_deb.sh`.
Expand Down Expand Up @@ -140,14 +141,16 @@ make -j$(nproc)
| `LIBE3_ENABLE_ZMQ` | ON | Enable ZeroMQ transport |
| `LIBE3_ENABLE_ASN1` | ON | Enable ASN.1 encoding |
| `LIBE3_ENABLE_JSON` | OFF | Enable JSON encoding support |
| `LIBE3_ENABLE_PROTOBUF` | OFF | Enable Protocol Buffers encoding support |
| `LIBE3_ENABLE_ASAN` | OFF | Enable AddressSanitizer |
| `LIBE3_ENABLE_TSAN` | OFF | Enable ThreadSanitizer |
| `LIBE3_ENABLE_SWIG` | OFF | Build the SWIG-generated Python bindings (`_libe3py.so` + `libe3py.py`) |

> **Note on encoding selection:** `LIBE3_ENABLE_ASN1` and `LIBE3_ENABLE_JSON` are independent
> compile-time inclusion flags — both can be `ON` simultaneously to build a library that supports
> both encodings. The active encoding is **selected at runtime** via the `encoding` field of
> `e3_config_t` (0 = ASN1, 1 = JSON) when calling `e3_agent_create_with_config()`.
> **Note on encoding selection:** `LIBE3_ENABLE_ASN1`, `LIBE3_ENABLE_JSON`, and
> `LIBE3_ENABLE_PROTOBUF` are independent compile-time inclusion flags — any combination can be
> `ON` simultaneously to build a library that supports several encodings. The active encoding is
> **selected at runtime** via the `encoding` field of `e3_config_t` (0 = ASN1, 1 = JSON,
> 2 = PROTOBUF) when calling `e3_agent_create_with_config()`.

### Running Tests

Expand Down Expand Up @@ -371,7 +374,7 @@ for local development (IPC) or production deployments.
| `setup_endpoint` | string | Setup connection endpoint (default: ipc:///tmp/dapps/setup) |
| `subscriber_endpoint` | string | Subscriber endpoint (default: ipc:///tmp/dapps/dapp_socket) |
| `publisher_endpoint` | string | Publisher endpoint (default: ipc:///tmp/dapps/e3_socket) |
| `encoding` | EncodingFormat | ASN1 or JSON |
| `encoding` | EncodingFormat | ASN1, JSON, or PROTOBUF |
| `connect_timeout_ms` | uint32_t | Connect timeout (ms) |
| `recv_timeout_ms` | uint32_t | Receive timeout (ms) |
| `send_timeout_ms` | uint32_t | Send timeout (ms) |
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.6
0.0.7
25 changes: 21 additions & 4 deletions build_libe3
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ BUILD_EXAMPLES=1
ENABLE_ZMQ=1
ENABLE_ASN1=1
ENABLE_JSON=0
ENABLE_PROTOBUF=0
ENABLE_ASAN=0
ENABLE_TSAN=0
ENABLE_UBSAN=0
Expand Down Expand Up @@ -104,6 +105,8 @@ Features:
--disable-zmq Disable ZeroMQ transport
--enable-asn1 Enable ASN.1 encoding support (default: ON)
--disable-asn1 Disable ASN.1 encoding
--enable-protobuf Enable Protocol Buffers encoding support (default: OFF)
--disable-protobuf Disable Protocol Buffers encoding
--enable-tests Build unit tests (default: ON)
--disable-tests Disable unit tests
--enable-examples Build examples (default: ON)
Expand Down Expand Up @@ -225,7 +228,9 @@ install_dependencies() {
bison \
flex \
nlohmann-json3-dev \
libsctp-dev
libsctp-dev \
protobuf-compiler \
libprotobuf-dev
# Build and install asn1c from source (some distros ship old versions)
INSTALLER="apt-get"
install_asn1c_from_source
Expand All @@ -240,7 +245,9 @@ install_dependencies() {
git \
asn1c \
nlohmann-json-devel \
lksctp-tools-devel
lksctp-tools-devel \
protobuf-compiler \
protobuf-devel
elif check_command pacman; then
echo_info "Detected Arch Linux system"
sudo pacman -Sy --noconfirm \
Expand All @@ -252,10 +259,11 @@ install_dependencies() {
git \
asn1c \
nlohmann-json \
lksctp-tools
lksctp-tools \
protobuf
elif check_command brew; then
echo_info "Detected macOS with Homebrew"
brew install cmake pkg-config zeromq ninja asn1c nlohmann-json
brew install cmake pkg-config zeromq ninja asn1c nlohmann-json protobuf
else
echo_error "Unsupported package manager. Please install dependencies manually:"
echo " - cmake (>= 3.16)"
Expand Down Expand Up @@ -300,6 +308,7 @@ configure_cmake() {
"-DLIBE3_ENABLE_ZMQ=$([ $ENABLE_ZMQ -eq 1 ] && echo ON || echo OFF)"
"-DLIBE3_ENABLE_ASN1=$([ $ENABLE_ASN1 -eq 1 ] && echo ON || echo OFF)"
"-DLIBE3_ENABLE_JSON=$([ $ENABLE_JSON -eq 1 ] && echo ON || echo OFF)"
"-DLIBE3_ENABLE_PROTOBUF=$([ $ENABLE_PROTOBUF -eq 1 ] && echo ON || echo OFF)"
"-DLIBE3_ENABLE_ASAN=$([ $ENABLE_ASAN -eq 1 ] && echo ON || echo OFF)"
"-DLIBE3_ENABLE_TSAN=$([ $ENABLE_TSAN -eq 1 ] && echo ON || echo OFF)"
"-DLIBE3_BUILD_DOCS=$([ $BUILD_DOCS -eq 1 ] && echo ON || echo OFF)"
Expand Down Expand Up @@ -571,6 +580,14 @@ main() {
ENABLE_ASN1=0
shift
;;
--enable-protobuf)
ENABLE_PROTOBUF=1
shift
;;
--disable-protobuf)
ENABLE_PROTOBUF=0
shift
;;
--enable-tests)
BUILD_TESTS=1
shift
Expand Down
5 changes: 5 additions & 0 deletions cmake/libe3Config.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ if(@LIBE3_ENABLE_JSON@)
find_dependency(nlohmann_json 3.11 REQUIRED)
endif()

# If libe3 was built with Protobuf support, make downstream consumers find Protobuf
if(@LIBE3_ENABLE_PROTOBUF@)
find_dependency(Protobuf REQUIRED)
endif()

include("${CMAKE_CURRENT_LIST_DIR}/libe3Targets.cmake")

check_required_components(libe3)
28 changes: 28 additions & 0 deletions cmake/libe3Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ if(LIBE3_ENABLE_JSON)
endif()
endif()

# Required: Protocol Buffers (libprotobuf runtime + protoc compiler) for protobuf encoding
if(LIBE3_ENABLE_PROTOBUF)
find_package(Protobuf REQUIRED)
message(STATUS "Protobuf found: ${Protobuf_VERSION} (protoc: ${Protobuf_PROTOC_EXECUTABLE})")
endif()

# Required: tl::expected for C++17 std::expected-like functionality
include(FetchContent)
FetchContent_Declare(
Expand All @@ -43,6 +49,28 @@ message(STATUS "tl::expected: Fetched from GitHub")
# Optional Dependencies
# ============================================================================

# Optional: Google Benchmark for the integration micro-benchmarks
# (dev-only; never linked into the shipped library)
if(LIBE3_BUILD_INTEGRATION_TESTS)
find_package(benchmark 1.8 QUIET)
if(NOT benchmark_FOUND)
set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE)
set(BENCHMARK_ENABLE_INSTALL OFF CACHE BOOL "" FORCE)
set(BENCHMARK_INSTALL_DOCS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
googlebenchmark
GIT_REPOSITORY https://github.com/google/benchmark.git
GIT_TAG v1.9.1
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(googlebenchmark)
message(STATUS "Google Benchmark: Fetched from GitHub")
else()
message(STATUS "Google Benchmark: Found installed version")
endif()
endif()

# Optional: ZeroMQ
if(LIBE3_ENABLE_ZMQ)
find_package(PkgConfig QUIET)
Expand Down
Loading
Loading