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
51 changes: 51 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Unit Tests

on:
pull_request:
branches: [development]

concurrency:
group: unit-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
name: Build & Test (Ubuntu)
runs-on: ubuntu-latest
env:
CTEST_OUTPUT_ON_FAILURE: 1
CCACHE_DIR: ${{ github.workspace }}/.ccache
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ninja-build build-essential cmake \
libboost-all-dev libspdlog-dev libtinyxml2-dev libconfig++-dev \
libssl-dev libnl-3-dev zlib1g-dev ccache clang-tidy clang g++-12

- name: Configure (CMake)
run: |
cmake -S . -B build -GNinja -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release

- name: Build
run: cmake --build build --parallel

- name: List tests (debug)
run: ctest --test-dir build/tests -N

- name: Run unit tests
run: |
set -o pipefail
ctest --test-dir build/tests --output-on-failure -j 2 | tee build/ctest-log.txt

- name: Upload test log
if: always()
uses: actions/upload-artifact@v4
with:
name: ctest-log
path: build/ctest-log.txt
if-no-files-found: warn
13 changes: 10 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ pkg_check_modules(LIBCONFIG REQUIRED IMPORTED_TARGET libconfig++)
pkg_check_modules(ZLIB REQUIRED IMPORTED_TARGET zlib)
check_cxx_symbol_exists(mmap "sys/mman.h" HAVE_MMAP)

add_subdirectory(examples)
# Option to build example programs
option(BUILD_EXAMPLES "Build example transmitter/receiver programs" ON)
if (BUILD_EXAMPLES)
add_subdirectory(examples)
endif()

include_directories(
"${PROJECT_BINARY_DIR}"
Expand Down Expand Up @@ -56,8 +60,6 @@ target_include_directories(flute
${CMAKE_CURRENT_LIST_DIR}/include/
)

#add_library(flute src/Receiver.cpp src/Receiver.h src/AlcPacket.cpp src/File.cpp src/EncodingSymbol.cpp src/FileDeliveryTable.cpp)

target_link_libraries( flute
LINK_PUBLIC
spdlog::spdlog
Expand All @@ -69,3 +71,8 @@ target_link_libraries( flute
PkgConfig::ZLIB
)

# ---- Tests Subdirectory (optional) ----
option(BUILD_TESTING "Build unit tests" ON)
if (BUILD_TESTING AND NOT DEFINED GTEST_DISABLE)
add_subdirectory(tests)
endif()
63 changes: 52 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,98 +6,139 @@
</p>

## Introduction

Additional information can be found at: https://5g-mag.github.io/Getting-Started/pages/multimedia-content-delivery/

## Installation guide

Installation of libflute consists of 4 simple steps:

1. Getting the source code
2. Installing the dependencies
3. Build setup
4. Building

### Step 1: Getting the source code

````
cd ~
git clone https://github.com/5G-MAG/rt-libflute.git
````

### Step 2: Installing the dependencies

````
sudo apt install ninja-build libboost-all-dev libspdlog-dev libtinyxml2-dev libconfig++-dev clang-tidy clang g++-12
````

### Step 3: Build setup

````
cd rt-libflute/
mkdir build && cd build
cmake -GNinja ..
````

If you want to build the project without the unit tests run the following commands instead:

````
cd rt-libflute/
mkdir build && cd build
cmake -GNinja -DBUILD_TESTING=OFF ..
````

### Step 3: Building

````
ninja
````

## Usage

When installing libflute, it comes with two demo applications, a receiver and a transmitter. Both applications can be found under ``libflute/build/examples``.

When installing libflute, it comes with two demo applications, a receiver and a transmitter. Both applications can be
found under ``rt-libflute/build/examples``.

### Step 1: Setting up a Flute receiver

To start the Flute receiver type in

````
cd rt-libflute/build/examples
./flute-receiver
````

The application will listen at the multicast address 238.1.1.95 by default. Check the help page for additional options (``./flute-receiver --help``).
The application will listen at the multicast address 238.1.1.95 by default. Check the help page for additional options (
``./flute-receiver --help``).

### Step 2: Setting up a Flute transmitter

To start the Flute transmitter type in

````
cd rt-libflute/build/examples
./flute-transmitter -r 100000 file
````

For file enter a file that shall be transmitted.

The parameter -r provides a data rate limit in kbit/s.

> **Note:** Keep in mind, the rate limit should not be set higher than the network allows, otherwise packet loss can occur (UDP transmission).
> **Note:** Keep in mind, the rate limit should not be set higher than the network allows, otherwise packet loss can
> occur (UDP transmission).

### Optional: Using IPSec for secure transmission

If you want to ensure, that transmission between to parties shall be encrypted, you can activate IPSec.

Simply use the -k parameter on transmitter and receiver side with a. As IPSec key a AES 256-bit key (so 64 character long) is expected.

* Starting the receiver with IPSec key:
Simply use the -k parameter on transmitter and receiver side with a. As IPSec key a AES 256-bit key (so 64 character
long) is expected.

* Starting the receiver with IPSec key:

````
sudo ./flute-receiver -k fdce8eaf81e3da02fa67e07df975c0111ecfa906561e762e5f3e78dfe106498e
````
As soon as the receiver is starting with -k option, a policy is beeing created that ensures that incoming packets with a specific destination address (can be set with -m) are decrypted with the specified IPSec key.

As soon as the receiver is starting with -k option, a policy is beeing created that ensures that incoming packets with a
specific destination address (can be set with -m) are decrypted with the specified IPSec key.

You can check the policies with

````
sudo ip xfrm state list
sudo ip xfrm policy list
````

* Starting the transmitter with IPSec key:

````
sudo ./flute-transmitter -r 100000 -k fdce8eaf81e3da02fa67e07df975c0111ecfa906561e762e5f3e78dfe106498e file
````
Outgoing packages with a specific destination address (can be set with -m) will be encrypted with the specified IPSec key.

Outgoing packages with a specific destination address (can be set with -m) will be encrypted with the specified IPSec
key.

* Optional: Setting superuser rights

To allow the application to set policy entries without superuser privileges for IPSec, set its capabilities
To allow the application to set policy entries without superuser privileges for IPSec, set its capabilities
accordingly. Alternatively, you can run it with superuser rights (``sudo ...``).

````
sudo setcap 'cap_net_admin=eip' ./flute-transmitter
sudo setcap 'cap_net_admin=eip' ./flute-receiver
````

## Testing

To execute the unit tests make sure to have built the project with the unit tests enabled (see Step 3: Build setup).

Then run

````
cd build/tests
ctest
````

## Documentation

Documentation of the source code can be found at: https://5g-mag.github.io/rt-libflute/
40 changes: 40 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# CMake configuration for unit tests
# Assumes parent CMakeLists.txt set option(BUILD_TESTING ...)
cmake_minimum_required(VERSION 3.16)

# GoogleTest requires at least C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include(FetchContent)

# Fetch GoogleTest only if not already provided and not explicitly disabled

FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.17.0
GIT_SHALLOW TRUE
)

# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
FetchContent_MakeAvailable(googletest)

enable_testing()

add_executable(flute_tests
test_transmitter.cpp
)

# Link against library under test
target_link_libraries(
flute_tests
PRIVATE
flute
GTest::gtest_main
)

target_include_directories(flute_tests PRIVATE ${PROJECT_SOURCE_DIR}/include)
include(GoogleTest)
gtest_discover_tests(flute_tests)
75 changes: 75 additions & 0 deletions tests/test_transmitter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#include <gtest/gtest.h>
#include <boost/asio.hpp>
#include "Transmitter.h"

using namespace LibFlute;

// Helper to construct a Transmitter for tests
static std::unique_ptr<Transmitter> make_tx(boost::asio::io_context &io, uint32_t rate_limit = 0) {
// Use a multicast address and reasonable MTU
return std::make_unique<Transmitter>("239.1.1.1", 5000, /*tsi*/1234, /*mtu*/1400, rate_limit, io);
}

TEST(TransmitterGetterSetterTest, RateLimitGetterSetter) {
boost::asio::io_context io;
auto tx = make_tx(io, 0);
// Initial value should match constructor
EXPECT_EQ(tx->rate_limit(), 0u);
// Set new value
tx->rate_limit(1500);
EXPECT_EQ(tx->rate_limit(), 1500u);
// Chaining
tx->rate_limit(2000).rate_limit(3000);
EXPECT_EQ(tx->rate_limit(), 3000u);
}

TEST(TransmitterGetterSetterTest, EndpointSetterString) {
boost::asio::io_context io;
auto tx = make_tx(io);
// Initial endpoint
auto ep_initial = tx->endpoint();
EXPECT_EQ(ep_initial.address().to_string(), std::string("239.1.1.1"));
EXPECT_EQ(ep_initial.port(), 5000);
// Change via string overload
tx->endpoint("239.1.1.2", 6000);
auto ep_new = tx->endpoint();
EXPECT_EQ(ep_new.address().to_string(), std::string("239.1.1.2"));
EXPECT_EQ(ep_new.port(), 6000);
}

TEST(TransmitterGetterSetterTest, EndpointSetterEndpointObject) {
boost::asio::io_context io;
auto tx = make_tx(io);
boost::asio::ip::udp::endpoint new_ep(boost::asio::ip::make_address("239.1.1.3"), 7000);
tx->endpoint(new_ep);
auto ep = tx->endpoint();
EXPECT_EQ(ep.address().to_string(), std::string("239.1.1.3"));
EXPECT_EQ(ep.port(), 7000);
// Move overload
boost::asio::ip::udp::endpoint moved_ep(boost::asio::ip::make_address("239.1.1.4"), 8000);
tx->endpoint(std::move(moved_ep));
auto ep2 = tx->endpoint();
EXPECT_EQ(ep2.address().to_string(), std::string("239.1.1.4"));
EXPECT_EQ(ep2.port(), 8000);
}

TEST(TransmitterGetterSetterTest, UdpTunnelAddressSetAndUnset) {
boost::asio::io_context io;
auto tx = make_tx(io);
// Initially no tunnel endpoint
EXPECT_FALSE(tx->udp_tunnel_address().has_value());
// Set tunnel endpoint (copy overload)
boost::asio::ip::udp::endpoint tunnel_ep(boost::asio::ip::make_address("127.0.0.1"), 9000);
tx->udp_tunnel_address(tunnel_ep);
ASSERT_TRUE(tx->udp_tunnel_address().has_value());
EXPECT_EQ(tx->udp_tunnel_address()->address().to_string(), std::string("127.0.0.1"));
EXPECT_EQ(tx->udp_tunnel_address()->port(), 9000);
// Set tunnel endpoint (move overload) to a new value
boost::asio::ip::udp::endpoint tunnel_ep2(boost::asio::ip::make_address("127.0.0.1"), 9100);
tx->udp_tunnel_address(std::move(tunnel_ep2));
ASSERT_TRUE(tx->udp_tunnel_address().has_value());
EXPECT_EQ(tx->udp_tunnel_address()->port(), 9100);
// Unset
tx->udp_tunnel_address(std::nullopt);
EXPECT_FALSE(tx->udp_tunnel_address().has_value());
}