diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml new file mode 100644 index 0000000..d19d893 --- /dev/null +++ b/.github/workflows/unit-tests.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 58864a0..ab2b9e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}" @@ -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 @@ -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() diff --git a/README.md b/README.md index 0132996..e8c080c 100644 --- a/README.md +++ b/README.md @@ -6,98 +6,139 @@

## 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/ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..8df202f --- /dev/null +++ b/tests/CMakeLists.txt @@ -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) diff --git a/tests/test_transmitter.cpp b/tests/test_transmitter.cpp new file mode 100644 index 0000000..88e2de9 --- /dev/null +++ b/tests/test_transmitter.cpp @@ -0,0 +1,75 @@ +#include +#include +#include "Transmitter.h" + +using namespace LibFlute; + +// Helper to construct a Transmitter for tests +static std::unique_ptr make_tx(boost::asio::io_context &io, uint32_t rate_limit = 0) { + // Use a multicast address and reasonable MTU + return std::make_unique("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()); +}