From 655bbf190cd8601b366b355a6e736137005d1cfc Mon Sep 17 00:00:00 2001
From: Florian Reimold <11774314+FlorianReimold@users.noreply.github.com>
Date: Fri, 12 Jan 2024 07:34:47 +0100
Subject: [PATCH] Initial commit
---
.clang-tidy | 66 ++
.github/workflows/build-macos.yml | 47 ++
.github/workflows/build-ubuntu.yml | 143 ++++
.github/workflows/build-windows.yml | 166 ++++
.gitignore | 22 +
.gitmodules | 9 +
CMakeLists.txt | 101 +++
LICENSE | 177 ++++
README.md | 27 +
cmake/ecaludp-module/Findecaludp.cmake | 2 +
cmake/ecaludpConfig.cmake.in | 6 +
cpack_config.cmake | 20 +
ecaludp/CMakeLists.txt | 195 +++++
ecaludp/ecaludp_version.h.in | 5 +
ecaludp/include/ecaludp/error.h | 114 +++
ecaludp/include/ecaludp/owning_buffer.h | 55 ++
ecaludp/include/ecaludp/raw_memory.h | 153 ++++
ecaludp/include/ecaludp/socket.h | 156 ++++
ecaludp/src/protocol/datagram_builder_v5.cpp | 227 +++++
ecaludp/src/protocol/datagram_builder_v5.h | 48 ++
ecaludp/src/protocol/datagram_description.h | 56 ++
ecaludp/src/protocol/header_common.h | 29 +
ecaludp/src/protocol/header_v5.h | 62 ++
ecaludp/src/protocol/header_v6.h | 45 +
ecaludp/src/protocol/portable_endian.h | 134 +++
ecaludp/src/protocol/reassembly_v5.cpp | 288 +++++++
ecaludp/src/protocol/reassembly_v5.h | 94 +++
ecaludp/src/socket.cpp | 237 ++++++
ecaludp/version.cmake | 3 +
samples/ecaludp_sample/CMakeLists.txt | 41 +
samples/ecaludp_sample/src/main.cpp | 132 +++
samples/integration_test/CMakeLists.txt | 39 +
samples/integration_test/src/main.cpp | 65 ++
tests/ecaludp_private_test/CMakeLists.txt | 47 ++
.../src/fragmentation_v5_test.cpp | 774 ++++++++++++++++++
tests/ecaludp_test/CMakeLists.txt | 42 +
tests/ecaludp_test/src/atomic_signalable.h | 205 +++++
.../ecaludp_test/src/ecaludp_socket_test.cpp | 158 ++++
.../src/fragmentation_v5_test.cpp | 774 ++++++++++++++++++
thirdparty/asio | 1 +
thirdparty/asio-module/Findasio.cmake | 30 +
thirdparty/build-asio.cmake | 2 +
thirdparty/build-gtest.cmake | 19 +
thirdparty/build-recycle.cmake | 5 +
thirdparty/googletest | 1 +
thirdparty/googletest-module/FindGTest.cmake | 1 +
thirdparty/recycle | 1 +
thirdparty/recycle-module/Findrecycle.cmake | 1 +
48 files changed, 5025 insertions(+)
create mode 100644 .clang-tidy
create mode 100644 .github/workflows/build-macos.yml
create mode 100644 .github/workflows/build-ubuntu.yml
create mode 100644 .github/workflows/build-windows.yml
create mode 100644 .gitignore
create mode 100644 .gitmodules
create mode 100644 CMakeLists.txt
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 cmake/ecaludp-module/Findecaludp.cmake
create mode 100644 cmake/ecaludpConfig.cmake.in
create mode 100755 cpack_config.cmake
create mode 100644 ecaludp/CMakeLists.txt
create mode 100644 ecaludp/ecaludp_version.h.in
create mode 100644 ecaludp/include/ecaludp/error.h
create mode 100644 ecaludp/include/ecaludp/owning_buffer.h
create mode 100644 ecaludp/include/ecaludp/raw_memory.h
create mode 100644 ecaludp/include/ecaludp/socket.h
create mode 100644 ecaludp/src/protocol/datagram_builder_v5.cpp
create mode 100644 ecaludp/src/protocol/datagram_builder_v5.h
create mode 100644 ecaludp/src/protocol/datagram_description.h
create mode 100644 ecaludp/src/protocol/header_common.h
create mode 100644 ecaludp/src/protocol/header_v5.h
create mode 100644 ecaludp/src/protocol/header_v6.h
create mode 100644 ecaludp/src/protocol/portable_endian.h
create mode 100644 ecaludp/src/protocol/reassembly_v5.cpp
create mode 100644 ecaludp/src/protocol/reassembly_v5.h
create mode 100644 ecaludp/src/socket.cpp
create mode 100644 ecaludp/version.cmake
create mode 100644 samples/ecaludp_sample/CMakeLists.txt
create mode 100644 samples/ecaludp_sample/src/main.cpp
create mode 100644 samples/integration_test/CMakeLists.txt
create mode 100644 samples/integration_test/src/main.cpp
create mode 100644 tests/ecaludp_private_test/CMakeLists.txt
create mode 100644 tests/ecaludp_private_test/src/fragmentation_v5_test.cpp
create mode 100644 tests/ecaludp_test/CMakeLists.txt
create mode 100644 tests/ecaludp_test/src/atomic_signalable.h
create mode 100644 tests/ecaludp_test/src/ecaludp_socket_test.cpp
create mode 100644 tests/ecaludp_test/src/fragmentation_v5_test.cpp
create mode 160000 thirdparty/asio
create mode 100644 thirdparty/asio-module/Findasio.cmake
create mode 100644 thirdparty/build-asio.cmake
create mode 100644 thirdparty/build-gtest.cmake
create mode 100644 thirdparty/build-recycle.cmake
create mode 160000 thirdparty/googletest
create mode 100644 thirdparty/googletest-module/FindGTest.cmake
create mode 160000 thirdparty/recycle
create mode 100644 thirdparty/recycle-module/Findrecycle.cmake
diff --git a/.clang-tidy b/.clang-tidy
new file mode 100644
index 0000000..a78a584
--- /dev/null
+++ b/.clang-tidy
@@ -0,0 +1,66 @@
+---
+# Resons why specific warnings have been turned off:
+#
+# -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling
+# This warns about memcpy and wants us to use memcpy_s, which is not available in our gcc setup.
+#
+# -cppcoreguidelines-pro-type-vararg
+# This forbids using functions like printf, snprintf etc. We would like to use those either way.
+#
+# -misc-no-recursion
+# Recursion with functions can be an elegant way of solving recursive problems
+#
+# These checks have been disabled to keep compatibility with C++14:
+# -modernize-concat-nested-namespaces
+# -modernize-use-nodiscard
+#
+
+Checks: "-*,
+ clang-analyzer-*,
+ -clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,
+
+ bugprone-*,
+ -bugprone-easily-swappable-parameters,
+ -bugprone-implicit-widening-of-multiplication-result,
+ -bugprone-narrowing-conversions,
+
+ cppcoreguidelines-*,
+ -cppcoreguidelines-avoid-c-arrays,
+ -cppcoreguidelines-avoid-magic-numbers,
+ -cppcoreguidelines-macro-usage,
+ -cppcoreguidelines-narrowing-conversions,
+ -cppcoreguidelines-non-private-member-variables-in-classes,
+ -cppcoreguidelines-no-malloc,
+ -cppcoreguidelines-owning-memory,
+ -cppcoreguidelines-pro-bounds-array-to-pointer-decay,
+ -cppcoreguidelines-pro-bounds-pointer-arithmetic,
+ -cppcoreguidelines-pro-type-vararg,
+ -cppcoreguidelines-pro-type-reinterpret-cast,
+
+ misc-*,
+ -misc-include-cleaner,
+ -misc-non-private-member-variables-in-classes,
+ -misc-no-recursion,
+
+ modernize-*,
+ -modernize-avoid-c-arrays,
+ -modernize-pass-by-value,
+ -modernize-use-trailing-return-type,
+ -modernize-use-auto,
+ -modernize-concat-nested-namespaces,
+ -modernize-return-braced-init-list,
+ -modernize-use-nodiscard,
+
+ performance-*,
+
+ readability-*,
+ -readability-braces-around-statements,
+ -readability-identifier-length,
+ -readability-magic-numbers,
+ -readability-redundant-access-specifiers,
+ -readability-function-cognitive-complexity,
+ -readability-else-after-return,
+"
+WarningsAsErrors: ''
+HeaderFilterRegex: '^((?!/thirdparty/|/_deps/).)*$'
+FormatStyle: none
diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml
new file mode 100644
index 0000000..c245076
--- /dev/null
+++ b/.github/workflows/build-macos.yml
@@ -0,0 +1,47 @@
+name: macOS
+
+on:
+ push:
+ pull_request:
+ branches: [ master ]
+
+env:
+ # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+ BUILD_TYPE: Release
+
+jobs:
+ build-macos:
+ # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
+ # You can convert this to a matrix build if you need cross-platform coverage.
+ # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+ runs-on: macos-latest
+
+ steps:
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: 'true'
+ fetch-depth: 0
+
+ - name: Configure CMake
+ # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
+ # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
+ run: |
+ cmake -B ${{github.workspace}}/_build \
+ -DECALUDP_BUILD_SAMPLES=ON \
+ -DECALUDP_BUILD_TESTS=ON \
+ -DECALUDP_USE_BUILTIN_ASIO=ON \
+ -DECALUDP_USE_BUILTIN_RECYCLE=ON \
+ -DECALUDP_USE_BUILTIN_GTEST=ON \
+ -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
+ shell: bash
+
+ - name: Build
+ # Build your program with the given configuration
+ run: cmake --build ${{github.workspace}}/_build --config ${{env.BUILD_TYPE}}
+
+ - name: Run Tests
+ run: ctest -C Release -V
+ working-directory: ${{ github.workspace }}/_build
+
diff --git a/.github/workflows/build-ubuntu.yml b/.github/workflows/build-ubuntu.yml
new file mode 100644
index 0000000..f2662a9
--- /dev/null
+++ b/.github/workflows/build-ubuntu.yml
@@ -0,0 +1,143 @@
+name: Ubuntu
+
+on:
+ push:
+ pull_request:
+ branches: [ master ]
+
+env:
+ # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+ BUILD_TYPE: Release
+ PROJECT_NAME: ecaludp
+
+jobs:
+ build-ubuntu:
+
+ strategy:
+ matrix:
+ library_type: [static, shared, object]
+ os: [ubuntu-22.04, ubuntu-20.04]
+
+ # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
+ # You can convert this to a matrix build if you need cross-platform coverage.
+ # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+ runs-on: ${{ matrix.os }}
+
+ steps:
+
+ - name: Set Variables
+ run: |
+ if [[ '${{ matrix.library_type }}' == 'static' ]]; then
+ echo "build_shared_libs=OFF" >> "$GITHUB_ENV"
+ echo "ecaludp_library_type=STATIC" >> "$GITHUB_ENV"
+ echo "package_postfix=static" >> "$GITHUB_ENV"
+ elif [[ '${{ matrix.library_type }}' == 'shared' ]]; then
+ echo "build_shared_libs=ON" >> "$GITHUB_ENV"
+ echo "ecaludp_library_type=SHARED" >> "$GITHUB_ENV"
+ echo "package_postfix=shared" >> "$GITHUB_ENV"
+ elif [[ '${{ matrix.library_type }}' == 'object' ]]; then
+ echo "build_shared_libs=OFF" >> "$GITHUB_ENV"
+ echo "ecaludp_library_type=OBJECT" >> "$GITHUB_ENV"
+ echo "package_postfix=object" >> "$GITHUB_ENV"
+ fi
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: 'true'
+ fetch-depth: 0
+
+ ############################################
+ # Test-compile the project
+ ############################################
+
+ - name: Configure CMake
+ # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
+ # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
+ run: |
+ cmake -B ${{github.workspace}}/_build \
+ -DECALUDP_BUILD_SAMPLES=ON \
+ -DECALUDP_BUILD_TESTS=ON \
+ -DECALUDP_USE_BUILTIN_ASIO=ON \
+ -DECALUDP_USE_BUILTIN_RECYCLE=ON \
+ -DECALUDP_USE_BUILTIN_GTEST=ON \
+ -DECALUDP_LIBRARY_TYPE=${{env.ecaludp_library_type}} \
+ -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} \
+ -DBUILD_SHARED_LIBS=${{ env.build_shared_libs }}
+
+ - name: Build
+ # Build your program with the given configuration
+ run: cmake --build ${{github.workspace}}/_build --config ${{env.BUILD_TYPE}}
+
+ - name: Run Tests
+ run: ctest -C Release -V
+ working-directory: ${{ github.workspace }}/_build
+
+ - name: Read Project Version from CMakeCache
+ run: |
+ cmake_project_version_string=$(cat "${{github.workspace}}/_build/CMakeCache.txt" | grep "^CMAKE_PROJECT_VERSION:")
+ arr=(${cmake_project_version_string//=/ })
+ cmake_project_version=${arr[1]}
+ echo "CMAKE_PROJECT_VERSION=$cmake_project_version" >> "$GITHUB_ENV"
+ shell: bash
+
+ - name: CPack
+ run: cpack -G DEB
+ working-directory: ${{ github.workspace }}/_build
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Rename .deb installer
+ run: |
+ mv *.deb '${{ env.PROJECT_NAME }}-${{ env.CMAKE_PROJECT_VERSION }}-${{ matrix.os }}-${{ env.package_postfix }}.deb'
+ shell: bash
+ working-directory: ${{github.workspace}}/_build/_package/
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Upload binaries
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ env.PROJECT_NAME }}-${{ env.CMAKE_PROJECT_VERSION }}-${{ matrix.os }}-${{ env.package_postfix }}
+ path: ${{github.workspace}}/_build/_package/*.deb
+ if: ${{ matrix.library_type != 'object' }}
+
+ ############################################
+ # Test if our binary can be linked against
+ ############################################
+
+ - name: Install binaries
+ shell: bash
+ run: sudo dpkg -i ${{ github.workspace }}/_build/_package/*.deb
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Compile integration test (Release)
+ run: |
+ cmake -B ${{github.workspace}}/samples/integration_test/_build/release \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_MODULE_PATH="${{ github.workspace }}/thirdparty/asio-module/"
+
+ cmake --build ${{github.workspace}}/samples/integration_test/_build/release
+
+ working-directory: ${{ github.workspace }}/samples/integration_test
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Run integration test (Release)
+ run: ./integration_test
+ working-directory: ${{ github.workspace }}/samples/integration_test/_build/release
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Compile integration test (Debug)
+ run: |
+ cmake -B ${{github.workspace}}/samples/integration_test/_build/debug \
+ -DCMAKE_BUILD_TYPE=Debug \
+ -DCMAKE_MODULE_PATH="${{ github.workspace }}/thirdparty/asio-module/"
+
+ cmake --build ${{github.workspace}}/samples/integration_test/_build/debug
+
+ working-directory: ${{ github.workspace }}/samples/integration_test
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Run integration test (Debug)
+ run: ./integration_test
+ working-directory: ${{ github.workspace }}/samples/integration_test/_build/debug
+ if: ${{ matrix.library_type != 'object' }}
+
diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml
new file mode 100644
index 0000000..7e6a912
--- /dev/null
+++ b/.github/workflows/build-windows.yml
@@ -0,0 +1,166 @@
+name: Windows
+
+on:
+ push:
+ pull_request:
+ branches: [ master ]
+
+env:
+ # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
+ INSTALL_PREFIX: _install
+ PROJECT_NAME: ecaludp
+ VS_TOOLSET: v140
+ VS_NAME: vs2015
+
+jobs:
+ build-windows:
+
+ strategy:
+ matrix:
+ library_type: [static, shared, object]
+ build_arch: [x64, win32]
+
+ # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac.
+ # You can convert this to a matrix build if you need cross-platform coverage.
+ # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
+ runs-on: windows-2019
+
+ steps:
+
+ - name: Set Variables
+ run: |
+ if ( '${{ matrix.library_type }}' -eq 'static' )
+ {
+ echo "build_shared_libs=OFF" >> "$Env:GITHUB_ENV"
+ echo "ecaludp_library_type=STATIC" >> "$Env:GITHUB_ENV"
+ echo "package_postfix=static" >> "$Env:GITHUB_ENV"
+ }
+ elseif( '${{ matrix.library_type }}' -eq 'shared' )
+ {
+ echo "build_shared_libs=ON" >> "$Env:GITHUB_ENV"
+ echo "ecaludp_library_type=SHARED" >> "$Env:GITHUB_ENV"
+ echo "package_postfix=shared" >> "$Env:GITHUB_ENV"
+ }
+ elseif( '${{ matrix.library_type }}' -eq 'object' )
+ {
+ echo "build_shared_libs=OFF" >> "$Env:GITHUB_ENV"
+ echo "ecaludp_library_type=OBJECT" >> "$Env:GITHUB_ENV"
+ echo "package_postfix=object" >> "$Env:GITHUB_ENV"
+ }
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: 'true'
+ fetch-depth: 0
+
+ ############################################
+ # Test-compile the project
+ ############################################
+
+ - name: Configure CMake
+ # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
+ # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
+ shell: cmd
+ run: |
+ cmake -B ${{github.workspace}}/_build ^
+ -G "Visual Studio 16 2019" ^
+ -A ${{ matrix.build_arch }} ^
+ -T ${{ env.VS_TOOLSET }} ^
+ -DECALUDP_BUILD_SAMPLES=ON ^
+ -DECALUDP_BUILD_TESTS=ON ^
+ -DECALUDP_USE_BUILTIN_ASIO=ON ^
+ -DECALUDP_USE_BUILTIN_RECYCLE=ON ^
+ -DECALUDP_USE_BUILTIN_GTEST=ON ^
+ -DECALUDP_LIBRARY_TYPE=${{env.ecaludp_library_type}} ^
+ -DCMAKE_INSTALL_PREFIX=${{env.INSTALL_PREFIX}} ^
+ -DBUILD_SHARED_LIBS=${{ env.build_shared_libs }}
+
+ - name: Build (Release)
+ shell: cmd
+ run: |
+ cmake --build ${{github.workspace}}/_build --config Release --parallel
+
+ - name: Install (Release)
+ shell: cmd
+ run: |
+ cmake --build ${{github.workspace}}/_build --config Release --target INSTALL
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Build (Debug)
+ shell: cmd
+ run: |
+ cmake --build ${{github.workspace}}/_build --config Debug --parallel
+
+ - name: Install (Release)
+ shell: cmd
+ run: |
+ cmake --build ${{github.workspace}}/_build --config Debug --target INSTALL
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Run Tests
+ run: ctest -C Release -V
+ working-directory: ${{ github.workspace }}/_build
+
+ - name: Read Project Version from CMakeCache
+ run: |
+ $cmake_project_version_line = cat ${{github.workspace}}/_build/CMakeCache.txt | Select-String -Pattern ^CMAKE_PROJECT_VERSION:
+ $cmake_project_version = $cmake_project_version_line.Line.split("=")[1]
+ echo "CMAKE_PROJECT_VERSION=$cmake_project_version" >> "$Env:GITHUB_ENV"
+
+ - name: Upload binaries
+ uses: actions/upload-artifact@v3
+ with:
+ name: ${{ env.PROJECT_NAME }}-${{ env.CMAKE_PROJECT_VERSION }}-windows-${{ matrix.build_arch }}-${{ env.VS_NAME }}-${{ matrix.library_type }}
+ path: ${{github.workspace}}/${{env.INSTALL_PREFIX}}
+ if: ${{ matrix.library_type != 'object' }}
+
+ ############################################
+ # Test if our binary can be linked against
+ ############################################
+
+ - name: CMake integration test
+ shell: powershell
+ run: |
+ $module_path="${{github.workspace}}/thirdparty/asio-module"
+ $module_path_posix=$module_path.Replace('\', '/')
+
+ cmake -B "${{github.workspace}}/samples/integration_test/_build" `
+ -A ${{ matrix.build_arch }} `
+ -DCMAKE_PREFIX_PATH="${{github.workspace}}/${{env.INSTALL_PREFIX}}" `
+ -DCMAKE_MODULE_PATH="$module_path_posix"
+
+ working-directory: ${{ github.workspace }}/samples/integration_test
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Compile integration test (Release)
+ shell: cmd
+ run: cmake --build ${{github.workspace}}/samples/integration_test/_build --config Release
+ working-directory: ${{ github.workspace }}/samples/integration_test
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Run integration test (Release)
+ run: |
+ if ( '${{ matrix.library_type }}' -eq 'shared' )
+ {
+ $Env:Path = '${{github.workspace}}/${{env.INSTALL_PREFIX}}/bin;' + $Env:Path
+ }
+ .\integration_test.exe
+ working-directory: ${{ github.workspace }}/samples/integration_test/_build/Release
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Compile integration test (Debug)
+ shell: cmd
+ run: cmake --build ${{github.workspace}}/samples/integration_test/_build --config Debug
+ working-directory: ${{ github.workspace }}/samples/integration_test
+ if: ${{ matrix.library_type != 'object' }}
+
+ - name: Run integration test (Debug)
+ run: |
+ if ( '${{ matrix.library_type }}' -eq 'shared' )
+ {
+ $Env:Path = '${{github.workspace}}/${{env.INSTALL_PREFIX}}/bin;' + $Env:Path
+ }
+ .\integration_test.exe
+ working-directory: ${{ github.workspace }}/samples/integration_test/_build/Debug
+ if: ${{ matrix.library_type != 'object' }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ccabed3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+*Debug
+ipch
+
+*.sdf
+*.suo
+*.aps
+*.user
+*.opendb
+*.db
+*.vscode
+
+/_install
+/.vs
+/CMakeLists.txt.user
+
+# Build directories
+/_build*
+/build*
+/samples/integration_test/_build*
+
+# Temporary Vim files
+*.swp
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..0a83e87
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,9 @@
+[submodule "thirdparty/asio"]
+ path = thirdparty/asio
+ url = https://github.com/chriskohlhoff/asio.git
+[submodule "thirdparty/googletest"]
+ path = thirdparty/googletest
+ url = https://github.com/google/googletest.git
+[submodule "thirdparty/recycle"]
+ path = thirdparty/recycle
+ url = https://github.com/steinwurf/recycle.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..af5a4ae
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,101 @@
+################################################################################
+# Copyright (c) 2024 Continental Corporation
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+cmake_minimum_required(VERSION 3.13)
+
+include(CMakeDependentOption)
+
+# Project call
+include("${CMAKE_CURRENT_LIST_DIR}/ecaludp/version.cmake")
+project(ecaludp VERSION ${ECALUDP_VERSION_MAJOR}.${ECALUDP_VERSION_MINOR}.${ECALUDP_VERSION_PATCH})
+
+# Normalize backslashes from Windows paths
+file(TO_CMAKE_PATH "${CMAKE_MODULE_PATH}" CMAKE_MODULE_PATH)
+file(TO_CMAKE_PATH "${CMAKE_PREFIX_PATH}" CMAKE_PREFIX_PATH)
+message(STATUS "Module Path: ${CMAKE_MODULE_PATH}")
+message(STATUS "Prefix Path: ${CMAKE_PREFIX_PATH}")
+
+# CMake Options
+option(ECALUDP_BUILD_SAMPLES
+ "Build project samples."
+ ON)
+option(ECALUDP_BUILD_TESTS
+ "Build the eCAL UDP tests"
+ OFF)
+
+option(ECALUDP_USE_BUILTIN_ASIO
+ "Use the builtin asio submodule. If set to OFF, asio must be available from somewhere else (e.g. system libs)."
+ ON)
+option(ECALUDP_USE_BUILTIN_RECYCLE
+ "Use the builtin steinwurf::recycle submodule. If set to OFF, recycle must be available from somewhere else (e.g. system libs)."
+ ON)
+cmake_dependent_option(ECALUDP_USE_BUILTIN_GTEST
+ "Use the builtin GoogleTest submodule. Only needed if ECALUDP_BUILD_TESTS is ON. If set to OFF, GoogleTest must be available from somewhere else (e.g. system libs)."
+ ON # Default value if dependency is met
+ "ECALUDP_BUILD_TESTS" # Dependency
+ OFF) # Default value if dependency is not met
+
+# Set Debug postfix
+set(CMAKE_DEBUG_POSTFIX d)
+set(CMAKE_MINSIZEREL_POSTFIX minsize)
+set(CMAKE_RELWITHDEBINFO_POSTFIX reldbg)
+
+# Use builtin asio
+if (ECALUDP_USE_BUILTIN_ASIO)
+ include("${CMAKE_CURRENT_LIST_DIR}/thirdparty/build-asio.cmake")
+endif()
+
+# Use builtin recycle
+if (ECALUDP_USE_BUILTIN_RECYCLE)
+ include("${CMAKE_CURRENT_LIST_DIR}/thirdparty/build-recycle.cmake")
+endif()
+
+# Use builtin gtest
+if (ECALUDP_USE_BUILTIN_GTEST)
+ include("${CMAKE_CURRENT_LIST_DIR}/thirdparty/build-gtest.cmake")
+endif()
+
+# For tests we need to make sure that all shared libraries and executables are
+# put into the same directory. Otherwise the tests will fail on windows.
+if(ECALUDP_BUILD_TESTS AND BUILD_SHARED_LIBS AND ECALUDP_USE_BUILTIN_GTEST)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
+endif()
+
+# Add main ecaludp library
+add_subdirectory(ecaludp)
+
+# Add the ecaludp dummy module
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ecaludp-module)
+
+if (ECALUDP_BUILD_SAMPLES)
+ add_subdirectory(samples/ecaludp_sample)
+endif()
+
+if (ECALUDP_BUILD_TESTS)
+ enable_testing()
+ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tests/ecaludp_test")
+
+ # Check if ecaludp is a static lib. We can only add the private tests for
+ # static libs and object libs, as we need to have access to the private
+ # implementation details.
+ get_target_property(ecaludp_target_type ecaludp TYPE)
+ if ((ecaludp_target_type STREQUAL STATIC_LIBRARY) OR (ecaludp_target_type STREQUAL OBJECT_LIBRARY))
+ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tests/ecaludp_private_test")
+ endif()
+endif()
+
+# Make this package available for packing with CPack
+include("${CMAKE_CURRENT_LIST_DIR}/cpack_config.cmake")
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..4947287
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,177 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..893e26b
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
+
+# ecaludp
+
+ecaludp is the underlying implementation for UDP traffic in eCAL. It transparently fragments and reassembles messages to provide support for big messages.
+
+## Dependencies
+
+The following dependencies are required to build ecaludp:
+
+| **Dependency** | **License** | **Integration** |
+|----------------|-------------|-----------------|
+| [asio](https://github.com/chriskohlhoff/asio) | [Boost Software License](https://github.com/chriskohlhoff/asio/blob/master/asio/LICENSE_1_0.txt) | [git submodule](https://github.com/eclipse-ecal/ecaludp/tree/master/thirdparty) |
+| [recycle](https://github.com/steinwurf/recycle) | [BSD-3](https://github.com/steinwurf/recycle/blob/master/LICENSE.rst) | [git submodule](https://github.com/eclipse-ecal/ecaludp/tree/master/thirdparty) |
+
+## CMake Options
+
+You can set the following CMake Options to control how ecaludp is built:
+
+|**Option** | **Type** | **Default** | **Explanation** |
+|---------------------------------|----------|-------------|-----------------------------------------------------------------------------------------------------------------|
+| `ECALUDP_BUILD_SAMPLES` | `BOOL` | `ON` | Build the ecaludp sample project. |
+| `ECALUDP_BUILD_TESTS` | `BOOL` | `OFF` | Build the the ecaludp tests. Requires gtest to be available. If ecaludp is built as static or object library, additional tests will be built that test the internal implementation that is not available as public API. |
+| `ECALUDP_USE_BUILTIN_ASIO`| `BOOL`| `ON` | Use the builtin asio submodule. If set to `OFF`, asio must be available from somewhere else (e.g. system libs). |
+| `ECALUDP_USE_BUILTIN_RECYCLE`| `BOOL`| `ON` | Use the builtin steinwurf::recycle submodule. If set to `OFF`, recycle must be available from somewhere else (e.g. system libs). |
+| `ECALUDP_USE_BUILTIN_GTEST`| `BOOL`| `ON`
_(when building tests)_ | Use the builtin GoogleTest submodule. Only needed if `FINEFTP_SERVER_BUILD_TESTS` is `ON`. If set to `OFF`, GoogleTest must be available from somewhere else (e.g. system libs). |
+| `ECALUDP_LIBRARY_TYPE` | `STRING` | | Controls the library type of ecaludp. Currently supported are `STATIC` / `SHARED` / `OBJECT`. If set, this will override the regular `BUILD_SHARED_LIBS` CMake option. If not set, that option will be used. |
\ No newline at end of file
diff --git a/cmake/ecaludp-module/Findecaludp.cmake b/cmake/ecaludp-module/Findecaludp.cmake
new file mode 100644
index 0000000..3aff4af
--- /dev/null
+++ b/cmake/ecaludp-module/Findecaludp.cmake
@@ -0,0 +1,2 @@
+# Stub find script for in-source build of samples
+set(ecaludp_FOUND True)
diff --git a/cmake/ecaludpConfig.cmake.in b/cmake/ecaludpConfig.cmake.in
new file mode 100644
index 0000000..806b5d6
--- /dev/null
+++ b/cmake/ecaludpConfig.cmake.in
@@ -0,0 +1,6 @@
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+find_dependency(asio)
+
+INCLUDE("${CMAKE_CURRENT_LIST_DIR}/ecaludpTargets.cmake")
\ No newline at end of file
diff --git a/cpack_config.cmake b/cpack_config.cmake
new file mode 100755
index 0000000..2f3aae7
--- /dev/null
+++ b/cpack_config.cmake
@@ -0,0 +1,20 @@
+set(CPACK_PACKAGE_NAME ${PROJECT_NAME})
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A UDP-socket doing transparent fragmentation to support large packaes")
+set(CPACK_PACKAGE_VENDOR "Eclipse eCAL")
+set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION})
+set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
+set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
+set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
+set(CPACK_PACKAGE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/_package")
+
+set(CPACK_PACKAGE_CONTACT "florian.reimold@continental-corporation.com")
+
+set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Florian Reimold ")
+set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/eclipse-ecal/ecaludp")
+set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
+set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)
+
+set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_LIST_DIR}/LICENSE")
+set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_LIST_DIR}/README.md")
+
+include(CPack)
diff --git a/ecaludp/CMakeLists.txt b/ecaludp/CMakeLists.txt
new file mode 100644
index 0000000..b0a216e
--- /dev/null
+++ b/ecaludp/CMakeLists.txt
@@ -0,0 +1,195 @@
+################################################################################
+# Copyright (c) 2024 Continental Corporation
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License, Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+################################################################################
+
+cmake_minimum_required(VERSION 3.13)
+
+include("${CMAKE_CURRENT_LIST_DIR}/version.cmake")
+project(ecaludp VERSION ${ECALUDP_VERSION_MAJOR}.${ECALUDP_VERSION_MINOR}.${ECALUDP_VERSION_PATCH})
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+
+# Disable default export of symbols
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
+
+find_package(asio REQUIRED)
+find_package(recycle REQUIRED)
+
+# Include GenerateExportHeader that will create export macros for us
+include(GenerateExportHeader)
+
+# Public API include directory
+set (includes
+ include/ecaludp/error.h
+ include/ecaludp/owning_buffer.h
+ include/ecaludp/raw_memory.h
+ include/ecaludp/socket.h
+)
+
+# Private source files
+set(sources
+ src/socket.cpp
+ src/protocol/datagram_builder_v5.cpp
+ src/protocol/datagram_builder_v5.h
+ src/protocol/datagram_description.h
+ src/protocol/header_common.h
+ src/protocol/header_v5.h
+ src/protocol/header_v6.h
+ src/protocol/portable_endian.h
+ src/protocol/reassembly_v5.cpp
+ src/protocol/reassembly_v5.h
+)
+
+# Build as object library
+add_library (${PROJECT_NAME} ${ECALUDP_LIBRARY_TYPE}
+ ${includes}
+ ${sources}
+)
+
+# Generate version defines
+configure_file("ecaludp_version.h.in" "${PROJECT_BINARY_DIR}/include/ecaludp/ecaludp_version.h" @ONLY)
+
+# Generate header with export macros
+generate_export_header(${PROJECT_NAME}
+ EXPORT_FILE_NAME ${PROJECT_BINARY_DIR}/include/ecaludp/ecaludp_export.h
+ BASE_NAME ECALUDP
+)
+
+add_library (ecaludp::ecaludp ALIAS ${PROJECT_NAME})
+
+target_link_libraries(${PROJECT_NAME}
+ PUBLIC
+ asio::asio
+ PRIVATE
+ # Link header-only libs (recycle) as described in this workaround:
+ # https://gitlab.kitware.com/cmake/cmake/-/issues/15415#note_633938
+ $
+ $<$:ws2_32>
+ $<$:wsock32>
+)
+
+target_compile_definitions(${PROJECT_NAME}
+ PRIVATE
+ ASIO_STANDALONE
+ _WIN32_WINNT=0x0601
+)
+
+# Check if ecaludp is a static lib. We can only add the private tests for
+# static libs and object libs, as we need to have access to the private
+# implementation details.
+get_target_property(ecaludp_target_type ecaludp TYPE)
+if (ecaludp_target_type STREQUAL OBJECT_LIBRARY)
+ target_compile_definitions(${PROJECT_NAME}
+ PUBLIC
+ ECALUDP_STATIC_DEFINE
+ )
+endif()
+
+target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_14)
+
+target_compile_options(${PROJECT_NAME} PRIVATE
+ $<$,$,$>:
+ -Wall -Wextra>
+ $<$:
+ /W4>)
+
+
+# Add own public include directory
+target_include_directories(${PROJECT_NAME}
+ PUBLIC
+ $
+ $ # To find the export file generated by generate_export_header
+ $
+ PRIVATE
+ "src/"
+)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES
+ OUTPUT_NAME ${PROJECT_NAME}
+ FOLDER ecal/udp
+)
+
+##################################
+
+source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES
+ ${includes}
+ ${sources}
+)
+
+
+################################################################################
+### Installation rules
+################################################################################
+
+set(ECALUDP_INSTALL_CMAKE_DIR "lib/cmake/ecaludp")
+
+# Install Runtime & Libs
+install(
+ TARGETS ${PROJECT_NAME}
+ EXPORT ecaludpTargets
+
+ RUNTIME
+ DESTINATION "bin"
+ COMPONENT ecaludp_runtime
+
+ LIBRARY
+ DESTINATION "lib"
+ COMPONENT ecaludp_runtime
+
+ ARCHIVE
+ DESTINATION "lib"
+ COMPONENT ecaludp_dev
+)
+
+# Install public header files (-> dev package)
+install(
+ DIRECTORY "include/ecaludp"
+ DESTINATION "include"
+ COMPONENT ecaludp_dev
+ FILES_MATCHING PATTERN "*.h"
+)
+
+# Install the auto-generated header with the export macros (-> dev package)
+install(
+ DIRECTORY "${PROJECT_BINARY_DIR}/include/ecaludp"
+ DESTINATION "include"
+ COMPONENT ecaludp_dev
+ FILES_MATCHING PATTERN "*.h"
+)
+
+install(
+ EXPORT ecaludpTargets
+ FILE ecaludpTargets.cmake
+ DESTINATION ${ECALUDP_INSTALL_CMAKE_DIR}
+ NAMESPACE ecaludp::
+ COMPONENT ecaludp_dev
+)
+
+# Create and install Config.cmake file (-> dev package)
+
+include(CMakePackageConfigHelpers)
+
+configure_package_config_file(
+ "../cmake/ecaludpConfig.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/cmake_/ecaludpConfig.cmake"
+ INSTALL_DESTINATION ${ECALUDP_INSTALL_CMAKE_DIR}
+ PATH_VARS ECALUDP_INSTALL_CMAKE_DIR
+)
+install(
+ FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake_/ecaludpConfig.cmake"
+ DESTINATION ${ECALUDP_INSTALL_CMAKE_DIR}
+ COMPONENT ecaludp_dev
+)
\ No newline at end of file
diff --git a/ecaludp/ecaludp_version.h.in b/ecaludp/ecaludp_version.h.in
new file mode 100644
index 0000000..8172028
--- /dev/null
+++ b/ecaludp/ecaludp_version.h.in
@@ -0,0 +1,5 @@
+#pragma once
+
+#define ECALUDP_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
+#define ECALUDP_VERSION_MINOR @PROJECT_VERSION_MINOR@
+#define ECALUDP_VERSION_PATCH @PROJECT_VERSION_PATCH@
diff --git a/ecaludp/include/ecaludp/error.h b/ecaludp/include/ecaludp/error.h
new file mode 100644
index 0000000..e26d4fe
--- /dev/null
+++ b/ecaludp/include/ecaludp/error.h
@@ -0,0 +1,114 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+#pragma once
+
+#include
+
+namespace ecaludp
+{
+ class Error
+ {
+ //////////////////////////////////////////
+ // Data model
+ //////////////////////////////////////////
+ public:
+ enum ErrorCode
+ {
+ // Generic
+ OK,
+ GENERIC_ERROR,
+
+ // Receiving
+ UNSUPPORTED_PROTOCOL_VERSION,
+
+ DUPLICATE_DATAGRAM,
+ MALFORMED_DATAGRAM,
+ MALFORMED_REASSEMBLED_MESSAGE,
+ };
+
+ //////////////////////////////////////////
+ // Constructor & Destructor
+ //////////////////////////////////////////
+ public:
+ Error(ErrorCode error_code, const std::string& message) : error_code_(error_code), message_(message) {}
+ Error(ErrorCode error_code) : error_code_(error_code) {}
+
+ // Copy constructor & assignment operator
+ Error(const Error& other) = default;
+ Error& operator=(const Error& other) = default;
+
+ // Move constructor & assignment operator
+ Error(Error&& other) = default;
+ Error& operator=(Error&& other) = default;
+
+ ~Error() = default;
+
+ //////////////////////////////////////////
+ // Public API
+ //////////////////////////////////////////
+ public:
+ inline std::string GetDescription() const
+ {
+ switch (error_code_)
+ {
+ // Generic
+ case OK: return "OK"; break;
+ case GENERIC_ERROR: return "Error"; break;
+
+ case UNSUPPORTED_PROTOCOL_VERSION: return "Unsupported protocol version"; break;
+ case DUPLICATE_DATAGRAM: return "Duplicate datagram"; break;
+ case MALFORMED_DATAGRAM: return "Malformed datagram"; break;
+ case MALFORMED_REASSEMBLED_MESSAGE: return "Malformed reassembled message"; break;
+
+ default: return "Unknown error";
+ }
+ }
+
+ inline std::string ToString() const
+ {
+ return (message_.empty() ? GetDescription() : GetDescription() + " (" + message_ + ")");
+ }
+
+ const inline std::string& GetMessage() const
+ {
+ return message_;
+ }
+
+ //////////////////////////////////////////
+ // Operators
+ //////////////////////////////////////////
+ inline operator bool() const { return error_code_ != ErrorCode::OK; }
+ inline bool operator== (const Error& other) const { return error_code_ == other.error_code_; }
+ inline bool operator== (const ErrorCode other) const { return error_code_ == other; }
+ inline bool operator!= (const Error& other) const { return error_code_ != other.error_code_; }
+ inline bool operator!= (const ErrorCode other) const { return error_code_ != other; }
+
+ inline Error& operator=(ErrorCode error_code)
+ {
+ error_code_ = error_code;
+ return *this;
+ }
+
+ //////////////////////////////////////////
+ // Member Variables
+ //////////////////////////////////////////
+ private:
+ ErrorCode error_code_;
+ std::string message_;
+ };
+
+} // namespace ecaludp
diff --git a/ecaludp/include/ecaludp/owning_buffer.h b/ecaludp/include/ecaludp/owning_buffer.h
new file mode 100644
index 0000000..b97c50d
--- /dev/null
+++ b/ecaludp/include/ecaludp/owning_buffer.h
@@ -0,0 +1,55 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+#include
+
+namespace ecaludp
+{
+ class OwningBuffer
+ {
+ public:
+ // Default constructor
+ OwningBuffer()
+ : data_ (nullptr)
+ , size_ (0)
+ , owning_container_(nullptr)
+ {}
+
+ // Constructor
+ OwningBuffer(const void* data, size_t size, const std::shared_ptr& owning_container)
+ : data_ (data)
+ , size_ (size)
+ , owning_container_(owning_container)
+ {}
+
+ const void* data() const
+ {
+ return data_;
+ }
+
+ size_t size() const
+ {
+ return size_;
+ }
+
+ private:
+ const void* data_;
+ const size_t size_;
+ const std::shared_ptr owning_container_;
+ };
+}
diff --git a/ecaludp/include/ecaludp/raw_memory.h b/ecaludp/include/ecaludp/raw_memory.h
new file mode 100644
index 0000000..e38f316
--- /dev/null
+++ b/ecaludp/include/ecaludp/raw_memory.h
@@ -0,0 +1,153 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace ecaludp
+{
+ class RawMemory
+ {
+ public:
+ // Default constructor
+ RawMemory()
+ : data_ (nullptr)
+ , capacity_(0)
+ , size_ (0)
+ {}
+
+ // Constructor
+ RawMemory(size_t size, bool overprovisioning = true)
+ : data_ (malloc(overprovisioning? size * 2 : size))
+ , capacity_(overprovisioning? size * 2 : size)
+ , size_ (size)
+ {}
+
+ // Copy constructor
+ RawMemory(const RawMemory& other)
+ : data_ (malloc(other.capacity_))
+ , capacity_(other.capacity_)
+ , size_ (other.size_)
+ {
+ if ((data_ != nullptr) && (other.data_ != nullptr))
+ memcpy(data_, other.data_, size_);
+ }
+
+ // Move constructor
+ RawMemory(RawMemory&& other) noexcept
+ : data_ (other.data_)
+ , capacity_(other.capacity_)
+ , size_ (other.size_)
+ {
+ other.data_ = nullptr;
+ other.capacity_ = 0;
+ other.size_ = 0;
+ }
+
+ // Copy assignment operator
+ RawMemory& operator=(const RawMemory& other)
+ {
+ if (this != &other)
+ {
+ resize(other.capacity_, false);
+ resize(other.size_);
+ if ((data_ != nullptr) && (other.data_ != nullptr))
+ memcpy(data_, other.data_, size_);
+ }
+ return *this;
+ }
+
+ // Move assignment operator
+ RawMemory& operator=(RawMemory&& other) noexcept
+ {
+ if (this != &other)
+ {
+ free(data_);
+
+ data_ = other.data_;
+ capacity_ = other.capacity_;
+ size_ = other.size_;
+
+ other.data_ = nullptr;
+ other.capacity_ = 0;
+ other.size_ = 0;
+ }
+ return *this;
+ }
+
+ // Destructor
+ ~RawMemory()
+ {
+ free(data_);
+ }
+
+ void swap(RawMemory& other) noexcept
+ {
+ std::swap(data_, other.data_);
+ std::swap(capacity_, other.capacity_);
+ std::swap(size_, other.size_);
+ }
+
+ uint8_t* data() const
+ {
+ return reinterpret_cast(data_);
+ }
+
+ size_t size() const
+ {
+ return size_;
+ }
+
+ void resize(size_t size, bool overprovisioning = true)
+ {
+ // If the new size is smaller than the current capacity, we can just set the new size
+ if (size <= capacity_)
+ {
+ size_ = size;
+ return;
+ }
+
+ // If the new size is larger than the current capacity, we need to allocate new memory
+
+ // Overprovisioning means that we allocate twice the size of the requested
+ // size. This is useful to not have to reallocate memory too often, if the
+ // user resizes it to a slightly bigger size.
+ const size_t new_capacity = (overprovisioning ? size * 2 : size);
+
+ // Allocate new memory
+ void* new_data = realloc(data_, new_capacity);
+ if (new_data == nullptr)
+ {
+ throw std::bad_alloc(); // TODO: decide if throwing here is the right thing to do. Also decide if we should throw at the other places as well.
+ }
+ else
+ {
+ data_ = new_data;
+ capacity_ = new_capacity;
+ size_ = size;
+ }
+ }
+
+ private:
+ void* data_;
+ size_t capacity_;
+ size_t size_;
+ };
+}
diff --git a/ecaludp/include/ecaludp/socket.h b/ecaludp/include/ecaludp/socket.h
new file mode 100644
index 0000000..dad23b9
--- /dev/null
+++ b/ecaludp/include/ecaludp/socket.h
@@ -0,0 +1,156 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace ecaludp
+{
+ namespace v5
+ {
+ class Reassembly;
+ }
+
+ class recycle_shared_pool;
+
+ class Socket
+ {
+ /////////////////////////////////////////////////////////////////
+ // Constructor
+ /////////////////////////////////////////////////////////////////
+ public:
+ ECALUDP_EXPORT Socket(asio::io_service& io_service, std::array magic_header_bytes);
+
+ // Destructor
+ ECALUDP_EXPORT ~Socket();
+
+ // Disable copy constructor and assignment operator
+ Socket(const Socket&) = delete;
+ Socket& operator=(const Socket&) = delete;
+
+ // Disable move constructor and assignment operator
+ Socket(Socket&&) = delete;
+ Socket& operator=(Socket&&) = delete;
+
+ /////////////////////////////////////////////////////////////////
+ // API Passthrough
+ /////////////////////////////////////////////////////////////////
+
+ bool at_mark() const { return socket_.at_mark(); }
+ bool at_mark(asio::error_code& ec) const { return socket_.at_mark(ec); }
+
+ std::size_t available() const { return socket_.available(); }
+ std::size_t available(asio::error_code& ec) const { return socket_.available(ec); }
+
+ void bind(const asio::ip::udp::endpoint& endpoint) { socket_.bind(endpoint); }
+ asio::error_code bind(const asio::ip::udp::endpoint& endpoint, asio::error_code& ec) { return socket_.bind(endpoint, ec); }
+
+ void cancel() { socket_.cancel(); }
+ asio::error_code cancel(asio::error_code& ec) { return socket_.cancel(ec); }
+
+ void close() { socket_.close(); }
+ asio::error_code close(asio::error_code& ec) { return socket_.close(ec); }
+
+ void connect(const asio::ip::udp::endpoint& peer_endpoint) { socket_.connect(peer_endpoint); }
+ asio::error_code connect(const asio::ip::udp::endpoint& peer_endpoint, asio::error_code& ec) { return socket_.connect(peer_endpoint, ec); }
+
+ const asio::any_io_executor& get_executor() { return socket_.get_executor(); }
+
+ template
+ void get_option(GettableSocketOption& option) { socket_.get_option(option); }
+
+ template
+ asio::error_code get_option(GettableSocketOption& option, asio::error_code& ec) { return socket_.get_option(option, ec); }
+
+ template
+ void io_control(IoControlCommand& command) { socket_.io_control(command); }
+
+ template
+ asio::error_code io_control(IoControlCommand& command, asio::error_code& ec) { return socket_.io_control(command, ec); }
+
+ bool is_open() const { return socket_.is_open(); }
+
+ asio::ip::udp::endpoint local_endpoint() const { return socket_.local_endpoint(); }
+ asio::ip::udp::endpoint local_endpoint(asio::error_code& ec) const { return socket_.local_endpoint(ec); }
+
+ void open(const asio::ip::udp& protocol) { socket_.open(protocol); }
+ asio::error_code open(const asio::ip::udp& protocol, asio::error_code& ec) { return socket_.open(protocol, ec); }
+
+ asio::ip::udp::endpoint remote_endpoint() const { return socket_.remote_endpoint(); }
+ asio::ip::udp::endpoint remote_endpoint(asio::error_code& ec) const { return socket_.remote_endpoint(ec); }
+
+ template
+ void set_option(const SettableSocketOption& option) { socket_.set_option(option); }
+
+ template
+ asio::error_code set_option(const SettableSocketOption& option, asio::error_code& ec) { return socket_.set_option(option, ec); }
+
+ void shutdown(asio::socket_base::shutdown_type what) { socket_.shutdown(what); }
+ asio::error_code shutdown(asio::socket_base::shutdown_type what, asio::error_code& ec) { return socket_.shutdown(what, ec); }
+
+ /////////////////////////////////////////////////////////////////
+ // Sending
+ /////////////////////////////////////////////////////////////////
+ public:
+ ECALUDP_EXPORT void async_send_to(const std::vector& buffer_sequence
+ , const asio::ip::udp::endpoint& destination
+ , const std::function& completion_handler);
+
+ ECALUDP_EXPORT void set_max_udp_datagram_size(std::size_t max_udp_datagram_size);
+ ECALUDP_EXPORT std::size_t get_max_udp_datagram_size() const;
+
+ ECALUDP_EXPORT void set_max_reassembly_age(std::chrono::steady_clock::duration max_reassembly_age);
+ ECALUDP_EXPORT std::chrono::steady_clock::duration get_max_reassembly_age() const;
+ /////////////////////////////////////////////////////////////////
+ // Receiving
+ /////////////////////////////////////////////////////////////////
+ public:
+ ECALUDP_EXPORT void async_receive_from(asio::ip::udp::endpoint& sender_endpoint
+ , const std::function&, asio::error_code)>& completion_handler);
+
+ private:
+ void receive_next_datagram_from(asio::ip::udp::endpoint& sender_endpoint
+ , const std::function&, asio::error_code)>& completion_handler);
+
+ std::shared_ptr handle_datagram(const std::shared_ptr& buffer
+ , const std::shared_ptr& sender_endpoint
+ , ecaludp::Error& error);
+
+ /////////////////////////////////////////////////////////////////
+ // Member Variables
+ /////////////////////////////////////////////////////////////////
+ private:
+ asio::ip::udp::socket socket_;
+ std::unique_ptr datagram_buffer_pool_;
+ std::unique_ptr reassembly_v5_;
+
+ std::array magic_header_bytes_;
+ std::size_t max_udp_datagram_size_;
+ std::chrono::steady_clock::duration max_reassembly_age_;
+ };
+}
diff --git a/ecaludp/src/protocol/datagram_builder_v5.cpp b/ecaludp/src/protocol/datagram_builder_v5.cpp
new file mode 100644
index 0000000..a7f3d5a
--- /dev/null
+++ b/ecaludp/src/protocol/datagram_builder_v5.cpp
@@ -0,0 +1,227 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+#include "datagram_builder_v5.h"
+
+#include "header_v5.h"
+#include "portable_endian.h"
+#include "protocol/datagram_description.h"
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ecaludp
+{
+ namespace v5
+ {
+
+ DatagramList create_datagram_list(const std::vector& buffer_sequence, size_t max_datagram_size, std::array magic_header_bytes)
+ {
+ // TODO: Complain when the max_udp_datagram_size is too small (the header doesn't even fit)
+
+ constexpr size_t header_size = sizeof(ecaludp::v5::Header);
+
+ size_t total_size = 0;
+ for (const auto& buffer : buffer_sequence)
+ {
+ total_size += buffer.size();
+ }
+
+ if ((total_size + header_size) <= max_datagram_size)
+ {
+ DatagramList datagram_list;
+ datagram_list.reserve(1);
+ datagram_list.emplace_back(create_non_fragmented_datagram(buffer_sequence, magic_header_bytes));
+
+ return datagram_list;
+ }
+ else
+ {
+ return create_fragmented_datagram_list(buffer_sequence, max_datagram_size, magic_header_bytes);
+ }
+ }
+
+ DatagramDescription create_non_fragmented_datagram(const std::vector& buffer_sequence, std::array magic_header_bytes)
+ {
+ uint32_t total_size = 0;
+ for (const auto& buffer : buffer_sequence)
+ {
+ total_size += static_cast(buffer.size());
+ }
+
+ // Container for the header buffer and all asio buffers
+ DatagramDescription datagram_description;
+
+ // Create one buffer for the header. That buffer will be the only additional
+ // needed buffer.
+ datagram_description.header_buffer_.resize(sizeof(ecaludp::v5::Header));
+
+ // Fill the header
+ auto* header_ptr = reinterpret_cast(datagram_description.header_buffer_.data());
+
+ header_ptr->magic[0] = magic_header_bytes[0];
+ header_ptr->magic[1] = magic_header_bytes[1];
+ header_ptr->magic[2] = magic_header_bytes[2];
+ header_ptr->magic[3] = magic_header_bytes[3];
+
+ header_ptr->version = 5;
+
+ header_ptr->type = static_cast(
+ htole32(static_cast(ecaludp::v5::message_type_uint32t::msg_type_non_fragmented_message)));
+ header_ptr->id = htole32(int32_t(-1)); // -1 => not fragmented
+ header_ptr->num = htole32(uint32_t(1)); // 1 => only 1 fragment
+ header_ptr->len = htole32(static_cast(total_size)); // denotes the length of the payload of this message only
+
+ // Add an asio buffer for the header buffer
+ datagram_description.asio_buffer_list_.emplace_back(datagram_description.header_buffer_.data(), datagram_description.header_buffer_.size());
+
+ // Add an asio buffer for each payload buffer
+ for (const auto& buffer : buffer_sequence)
+ {
+ datagram_description.asio_buffer_list_.emplace_back(buffer.data(), buffer.size());
+ }
+
+ return datagram_description;
+ }
+
+ DatagramList create_fragmented_datagram_list(const std::vector& buffer_sequence, size_t max_udp_datagram_size, std::array magic_header_bytes)
+ {
+ // Count the total size of all buffers
+ uint32_t total_size = 0;
+ for (const auto& buffer : buffer_sequence)
+ {
+ total_size += static_cast(buffer.size());
+ }
+
+ // Compute how many bytes we can send at once
+ const int payload_bytes_per_datagram = static_cast(max_udp_datagram_size) - sizeof(ecaludp::v5::Header);
+
+ // Compute how many datagrams we need. We need 1 datagram more, as
+ // the fragmentation info must be sent in a separate datagram
+ const uint32_t needed_fragment_count = ((total_size + (payload_bytes_per_datagram - 1)) / payload_bytes_per_datagram);
+ const uint32_t needed_datagram_count = 1 + needed_fragment_count;
+
+ // Pre-allocate the datagram list, so we never have to re-allocate
+ DatagramList datagram_list;
+ datagram_list.reserve(needed_datagram_count);
+
+ // Create a random number for the package ID, that is used to match all fragments
+ uint32_t message_id = 0;
+ {
+ static std::mutex mutex;
+ const std::lock_guard lock(mutex);
+ static uint32_t x = static_cast(std::chrono::duration_cast(
+ std::chrono::high_resolution_clock::now().time_since_epoch()).count()
+ );
+ static uint32_t y = 362436069;
+ static uint32_t z = 521288629;
+ message_id = xorshf96(x, y, z);
+ }
+
+ // Create the fragmentation info
+ {
+ datagram_list.emplace_back();
+ datagram_list.back().header_buffer_.resize(sizeof(ecaludp::v5::Header));
+
+ auto* fragment_info_header_ptr = reinterpret_cast(datagram_list.back().header_buffer_.data());
+
+ fragment_info_header_ptr->magic[0] = magic_header_bytes[0];
+ fragment_info_header_ptr->magic[1] = magic_header_bytes[1];
+ fragment_info_header_ptr->magic[2] = magic_header_bytes[2];
+ fragment_info_header_ptr->magic[3] = magic_header_bytes[3];
+
+ fragment_info_header_ptr->version = 5;
+
+ fragment_info_header_ptr->type = static_cast(
+ htole32(static_cast(ecaludp::v5::message_type_uint32t::msg_type_fragmented_message_info)));
+ fragment_info_header_ptr->id = htole32(message_id);
+ fragment_info_header_ptr->num = htole32(needed_fragment_count);
+ fragment_info_header_ptr->len = htole32(total_size); // denotes the length of the entire payload
+
+ // Add an asio buffer for the header buffer
+ datagram_list.back().asio_buffer_list_.emplace_back(datagram_list.back().header_buffer_.data(), datagram_list.back().header_buffer_.size());
+ }
+
+ // Iterate over all buffers and create fragments for them
+ size_t buffer_index = 0;
+ size_t offset_in_current_buffer = 0;
+
+ while ((buffer_index < buffer_sequence.size()
+ || ((buffer_index == (buffer_sequence.size() - 1))
+ && (offset_in_current_buffer < buffer_sequence[buffer_index].size()))))
+ {
+ // Create a new datagram, if the last one is full, or if this is the first real datagram (besides the fragmentation info)
+ // TODO: Only add a new datagram, if there are more bytes to be sent. The next buffer MAY be empty, so we don't need to add a new datagram.
+ if ((datagram_list.size() <= 1)
+ || (datagram_list.back().size() >= max_udp_datagram_size))
+ {
+ datagram_list.emplace_back();
+ datagram_list.back().header_buffer_.resize(sizeof(ecaludp::v5::Header));
+
+ auto* const header_ptr = reinterpret_cast(datagram_list.back().header_buffer_.data());
+
+ header_ptr->magic[0] = magic_header_bytes[0];
+ header_ptr->magic[1] = magic_header_bytes[1];
+ header_ptr->magic[2] = magic_header_bytes[2];
+ header_ptr->magic[3] = magic_header_bytes[3];
+
+ header_ptr->version = 5;
+
+ header_ptr->type = static_cast(
+ htole32(static_cast(ecaludp::v5::message_type_uint32t::msg_type_fragment)));
+ header_ptr->id = htole32(message_id);
+ header_ptr->num = htole32(static_cast(datagram_list.size() - 2)); // -1, because the first datagram is the fragmentation info
+ header_ptr->len = htole32(static_cast(0)); // denotes the length of the entire payload
+
+ // Add an asio buffer for the header buffer
+ datagram_list.back().asio_buffer_list_.emplace_back(datagram_list.back().header_buffer_.data(), datagram_list.back().header_buffer_.size());
+ }
+
+ // TODO: Handle 0-byte-buffers. Those can be discarded directly
+ // TODO: Also test the 0-byte-buffers.
+
+ // Compute how many bytes from the current buffer we can fit in the datagram
+ const size_t bytes_to_fit_in_current_datagram = std::min(max_udp_datagram_size - datagram_list.back().size(), buffer_sequence[buffer_index].size() - offset_in_current_buffer);
+
+ // Add an asio buffer to the datagram that points to the data in the user buffer
+ datagram_list.back().asio_buffer_list_.emplace_back(reinterpret_cast(buffer_sequence[buffer_index].data()) + offset_in_current_buffer, bytes_to_fit_in_current_datagram);
+
+ // Increase the size of the current datagram
+ auto* const header_ptr = reinterpret_cast(datagram_list.back().header_buffer_.data());
+ header_ptr->len = htole32(le32toh(header_ptr->len) + static_cast(bytes_to_fit_in_current_datagram));
+
+ // Increase the offset in the user buffer
+ offset_in_current_buffer += bytes_to_fit_in_current_datagram;
+
+ // Check if we reached the end of the current user buffer
+ if (offset_in_current_buffer >= buffer_sequence[buffer_index].size())
+ {
+ // Increase the buffer index
+ buffer_index++;
+
+ // Reset the offset in the current buffer
+ offset_in_current_buffer = 0;
+ }
+ }
+
+ // return the datagram list
+ return datagram_list;
+ }
+ }
+}
diff --git a/ecaludp/src/protocol/datagram_builder_v5.h b/ecaludp/src/protocol/datagram_builder_v5.h
new file mode 100644
index 0000000..b20f2d5
--- /dev/null
+++ b/ecaludp/src/protocol/datagram_builder_v5.h
@@ -0,0 +1,48 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include "datagram_description.h"
+#include
+#include
+#include
+
+namespace ecaludp
+{
+ namespace v5
+ {
+ DatagramList create_datagram_list(const std::vector& buffer_sequence, size_t max_datagram_size, std::array magic_header_bytes);
+
+ DatagramDescription create_non_fragmented_datagram(const std::vector& buffer_sequence, std::array magic_header_bytes);
+
+ DatagramList create_fragmented_datagram_list(const std::vector& buffer_sequence, size_t max_udp_datagram_size, std::array magic_header_bytes);
+
+ inline uint32_t xorshf96(uint32_t& x, uint32_t& y, uint32_t& z)
+ {
+ uint32_t t = 0;
+ x ^= x << 16;
+ x ^= x >> 5;
+ x ^= x << 1;
+
+ t = x;
+ x = y;
+ y = z;
+ z = t ^ x ^ y;
+
+ return z;
+ }
+ }
+}
diff --git a/ecaludp/src/protocol/datagram_description.h b/ecaludp/src/protocol/datagram_description.h
new file mode 100644
index 0000000..4c6706f
--- /dev/null
+++ b/ecaludp/src/protocol/datagram_description.h
@@ -0,0 +1,56 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace ecaludp {
+class DatagramDescription
+{
+ public:
+ // Default constructor
+ DatagramDescription() = default;
+
+ // Delete Copy constructor and assignment operator
+ DatagramDescription(const DatagramDescription&) = delete;
+ DatagramDescription& operator=(const DatagramDescription&) = delete;
+
+ // Default Move constructor and assignment operator
+ DatagramDescription(DatagramDescription&&) = default;
+ DatagramDescription& operator=(DatagramDescription&&) = default;
+
+ ~DatagramDescription() = default;
+
+ public:
+ std::vector header_buffer_ {};
+ std::vector asio_buffer_list_{};
+
+ size_t size() const
+ {
+ size_t size = 0;
+ for (const auto& buffer : asio_buffer_list_)
+ {
+ size += buffer.size();
+ }
+ return size;
+ }
+ };
+
+ using DatagramList = std::vector;
+ } // namespace ecaludp
diff --git a/ecaludp/src/protocol/header_common.h b/ecaludp/src/protocol/header_common.h
new file mode 100644
index 0000000..f9dc7b2
--- /dev/null
+++ b/ecaludp/src/protocol/header_common.h
@@ -0,0 +1,29 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+
+namespace ecaludp
+{
+#pragma pack(push, 1)
+ struct HeaderCommon
+ {
+ char magic[4];
+ uint8_t version;
+ };
+#pragma pack(pop)
+}
diff --git a/ecaludp/src/protocol/header_v5.h b/ecaludp/src/protocol/header_v5.h
new file mode 100644
index 0000000..957cfa0
--- /dev/null
+++ b/ecaludp/src/protocol/header_v5.h
@@ -0,0 +1,62 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+
+namespace ecaludp
+{
+ namespace v5
+ {
+ enum class message_type_uint32t : uint32_t
+ {
+ msg_type_unknown = 0,
+ msg_type_fragmented_message_info = 1, // former name: msg_type_header
+ msg_type_fragment = 2, // former name: msg_type_content
+ msg_type_non_fragmented_message = 3 // former name: msg_type_header_with_content
+ };
+
+ #pragma pack(push, 1)
+ struct Header
+ {
+ char magic[4];
+
+ uint8_t version; /// Header version. Must be 5 for this version 5 header
+ uint8_t reserved1; /// Must be sent as 0. The old implementation used this byte as 4-byte version (little endian), but never checked it. Thus, it may be used in the future.
+ uint8_t reserved2; /// Must be sent as 0. The old implementation used this byte as 4-byte version (little endian), but never checked it. Thus, it may be used in the future.
+ uint8_t reserved3; /// Must be sent as 0. The old implementation used this byte as 4-byte version (little endian), but never checked it. Thus, it may be used in the future.
+
+ message_type_uint32t type; /// The message type. See message_type_uint32t for possible values
+
+ int32_t id; /// Random ID to match fragmented parts of a message (Little-endian). Used differently depending on the message type:
+ /// - msg_type_fragmented_message_info: The Random ID that this fragmentation info will be applied to
+ /// - msg_type_fragment: The Random ID that this fragment belongs to. Used to match fragments to their fragmentation info
+ /// - msg_type_non_fragmented_message: Unused field. Must be sent as -1. Must not be evaluated.
+
+ uint32_t num; /// Fragment number (Little-endian). Used differently depending on the message type:
+ /// - msg_type_fragmented_message_info: Amount of fragments that this message was split into.
+ /// - msg_type_fragment: The number of this fragment
+ /// - msg_type_non_fragmented_message: Unused field. Must be sent as 1. Must not be evaluated.
+
+ uint32_t len; /// Payload length (Little-endian). Used differently depending on the message type. The payload must start directly after the header.
+ /// - msg_type_fragmented_message_info: Length of the original message before it got fragmented.
+ /// Messages of this type must not carry any payload themselves.
+ /// - msg_type_fragment: The payload lenght of this fragment
+ /// - msg_type_non_fragmented_message: The payload length of this message
+ };
+ #pragma pack(pop)
+ }
+}
\ No newline at end of file
diff --git a/ecaludp/src/protocol/header_v6.h b/ecaludp/src/protocol/header_v6.h
new file mode 100644
index 0000000..804cd02
--- /dev/null
+++ b/ecaludp/src/protocol/header_v6.h
@@ -0,0 +1,45 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+
+// TODO: Add V6
+namespace ecaludp
+{
+ namespace v6
+ {
+ #pragma pack(push, 1)
+ struct Header
+ {
+ unsigned char magic[4];
+
+ uint8_t version = 0;
+ uint8_t header_size = 0;
+ uint8_t flags = 0;
+ uint8_t reserved = 0;
+
+ uint32_t package_id = 0;
+ };
+
+ struct FragmentHeader
+ {
+ uint32_t total_length = 0;
+ uint32_t fragment_offset = 0;
+ };
+ #pragma pop
+ }
+}
\ No newline at end of file
diff --git a/ecaludp/src/protocol/portable_endian.h b/ecaludp/src/protocol/portable_endian.h
new file mode 100644
index 0000000..e6d238f
--- /dev/null
+++ b/ecaludp/src/protocol/portable_endian.h
@@ -0,0 +1,134 @@
+// This file has been taken from:
+// https://gist.github.com/panzi/6856583#file-portable_endian-h
+// It includes manual changes for the QNX platform
+//
+// "License": Public Domain
+// I, Mathias Panzenböck, place this file hereby into the public domain. Use it at your own risk for whatever you like.
+// In case there are jurisdictions that don't support putting things in the public domain you can also consider it to
+// be "dual licensed" under the BSD, MIT and Apache licenses, if you want to. This code is trivial anyway. Consider it
+// an example on how to get the endian conversion functions on different platforms.
+
+#ifndef PORTABLE_ENDIAN_H_
+#define PORTABLE_ENDIAN_H_
+
+#if (defined(_WIN16) || defined(_WIN32) || defined(_WIN64)) && !defined(__WINDOWS__)
+
+# define __WINDOWS__
+
+#endif
+
+#if defined(__linux__) || defined(__CYGWIN__)
+
+# include
+
+#elif defined(__APPLE__)
+
+# include
+
+# define htobe16(x) OSSwapHostToBigInt16(x)
+# define htole16(x) OSSwapHostToLittleInt16(x)
+# define be16toh(x) OSSwapBigToHostInt16(x)
+# define le16toh(x) OSSwapLittleToHostInt16(x)
+
+# define htobe32(x) OSSwapHostToBigInt32(x)
+# define htole32(x) OSSwapHostToLittleInt32(x)
+# define be32toh(x) OSSwapBigToHostInt32(x)
+# define le32toh(x) OSSwapLittleToHostInt32(x)
+
+# define htobe64(x) OSSwapHostToBigInt64(x)
+# define htole64(x) OSSwapHostToLittleInt64(x)
+# define be64toh(x) OSSwapBigToHostInt64(x)
+# define le64toh(x) OSSwapLittleToHostInt64(x)
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__OpenBSD__)
+
+# include
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__DragonFly__)
+
+# include
+
+# define be16toh(x) betoh16(x)
+# define le16toh(x) letoh16(x)
+
+# define be32toh(x) betoh32(x)
+# define le32toh(x) letoh32(x)
+
+# define be64toh(x) betoh64(x)
+# define le64toh(x) letoh64(x)
+
+#elif defined(__WINDOWS__)
+
+# include
+# ifdef __GNUC__
+# include
+# endif
+
+# if BYTE_ORDER == LITTLE_ENDIAN
+
+# define htobe16(x) htons(x)
+# define htole16(x) (x)
+# define be16toh(x) ntohs(x)
+# define le16toh(x) (x)
+
+# define htobe32(x) htonl(x)
+# define htole32(x) (x)
+# define be32toh(x) ntohl(x)
+# define le32toh(x) (x)
+
+# define htobe64(x) htonll(x)
+# define htole64(x) (x)
+# define be64toh(x) ntohll(x)
+# define le64toh(x) (x)
+
+# elif BYTE_ORDER == BIG_ENDIAN
+
+ /* that would be xbox 360 */
+# define htobe16(x) (x)
+# define htole16(x) __builtin_bswap16(x)
+# define be16toh(x) (x)
+# define le16toh(x) __builtin_bswap16(x)
+
+# define htobe32(x) (x)
+# define htole32(x) __builtin_bswap32(x)
+# define be32toh(x) (x)
+# define le32toh(x) __builtin_bswap32(x)
+
+# define htobe64(x) (x)
+# define htole64(x) __builtin_bswap64(x)
+# define be64toh(x) (x)
+# define le64toh(x) __builtin_bswap64(x)
+
+# else
+
+# error byte order not supported
+
+# endif
+
+# define __BYTE_ORDER BYTE_ORDER
+# define __BIG_ENDIAN BIG_ENDIAN
+# define __LITTLE_ENDIAN LITTLE_ENDIAN
+# define __PDP_ENDIAN PDP_ENDIAN
+
+#elif defined(__QNXNTO__)
+
+# include
+# include
+
+#else
+
+# error platform not supported
+
+#endif
+
+#endif
diff --git a/ecaludp/src/protocol/reassembly_v5.cpp b/ecaludp/src/protocol/reassembly_v5.cpp
new file mode 100644
index 0000000..f7b8804
--- /dev/null
+++ b/ecaludp/src/protocol/reassembly_v5.cpp
@@ -0,0 +1,288 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#include "reassembly_v5.h"
+
+#include "ecaludp/owning_buffer.h"
+#include "ecaludp/raw_memory.h"
+#include "header_v5.h"
+#include "portable_endian.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace ecaludp
+{
+ namespace v5
+ {
+ //////////////////////////////////////////////////////////////////////////////
+ // Constructor, Destructor
+ //////////////////////////////////////////////////////////////////////////////
+ Reassembly::Reassembly() = default;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Receiving, datagram handling & fragment reassembly
+ //////////////////////////////////////////////////////////////////////////////
+
+ std::shared_ptr Reassembly::handle_datagram(const std::shared_ptr& buffer, const std::shared_ptr& sender_endpoint, ecaludp::Error& error)
+ {
+ if (buffer->size() < sizeof(ecaludp::v5::Header))
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::MALFORMED_DATAGRAM, "Datagram too small, cannot contain V5 header. Size is " + std::to_string(buffer->size()) + " bytes.");
+ return nullptr;
+ }
+
+ const auto* header = reinterpret_cast(buffer->data());
+
+ // Each message type must be handled differently
+ if (static_cast(le32toh(static_cast(header->type)))
+ == ecaludp::v5::message_type_uint32t::msg_type_fragmented_message_info)
+ {
+ return handle_datagram_fragmented_message_info(buffer, sender_endpoint, error);
+ }
+ else if (static_cast(le32toh(static_cast(header->type)))
+ == ecaludp::v5::message_type_uint32t::msg_type_fragment)
+ {
+ return handle_datagram_fragment(buffer, sender_endpoint, error);
+ }
+ else if (static_cast(le32toh(static_cast(header->type)))
+ == ecaludp::v5::message_type_uint32t::msg_type_non_fragmented_message)
+ {
+ return handle_datagram_non_fragmented_message(buffer, error);
+ }
+ else
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::MALFORMED_DATAGRAM, "Invalid type");
+ return nullptr;
+ }
+ }
+
+ std::shared_ptr Reassembly::handle_datagram_fragmented_message_info(const std::shared_ptr& buffer, const std::shared_ptr& sender_endpoint, ecaludp::Error& error)
+ {
+ auto* header = reinterpret_cast(buffer->data());
+
+ const int32_t package_id = le32toh(header->id);
+ fragmented_package_key package_key{*sender_endpoint, package_id};
+
+ // Check if we already have a package with this id. If not, create one
+ auto existing_package_it = fragmented_packages_.find(package_key);
+ if (existing_package_it == fragmented_packages_.end())
+ {
+ existing_package_it = fragmented_packages_.emplace(package_key, fragmented_package{}).first;
+
+ }
+ else if (existing_package_it->second.first.fragment_info_received_)
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::DUPLICATE_DATAGRAM
+ , "Received fragment info for package " + std::to_string(package_id) + " twice");
+ return nullptr;
+ }
+
+ // Store that we received the fragment info
+ existing_package_it->second.first.fragment_info_received_ = true;
+
+ // Set the fragmentation info
+ existing_package_it->second.first.total_fragments_ = le32toh(header->num);
+ existing_package_it->second.first.total_size_bytes_ = le32toh(header->len);
+
+ // Resize the list of fragments, so we never have to resize again
+ existing_package_it->second.second.resize(existing_package_it->second.first.total_fragments_);
+
+ // Set the last access time
+ existing_package_it->second.first.last_access_ = std::chrono::steady_clock::now();
+
+ // Maybe the message is already complete. So let's check and reassemble the
+ // package if necessary
+ return handle_fragmented_package_if_complete(existing_package_it, error);
+ }
+
+ std::shared_ptr Reassembly::handle_datagram_fragment(const std::shared_ptr& buffer, const std::shared_ptr& sender_endpoint, ecaludp::Error& error)
+ {
+ auto* header = reinterpret_cast(buffer->data());
+
+ const int32_t package_id = le32toh(header->id);
+ fragmented_package_key package_key{*sender_endpoint, package_id};
+
+ // Check if we already have a package with this id. If not, create one
+ auto existing_package_it = fragmented_packages_.find(package_key);
+ if (existing_package_it == fragmented_packages_.end())
+ {
+ existing_package_it = fragmented_packages_.emplace(package_key, fragmented_package{}).first;
+ }
+
+ const uint32_t package_num = le32toh(header->num);
+
+ // Resize the list of fragments, if necessary. We only do that, if we didn't
+ // receive the fragment info yet, so we don't know how many fragments there
+ // will be, yet
+ if (!existing_package_it->second.first.fragment_info_received_)
+ {
+ existing_package_it->second.second.resize(std::max(existing_package_it->second.second.size(), static_cast(package_num + 1)));
+ }
+
+ // Check if this fragment number fits in the list of fragments.
+ if (package_num >= static_cast(existing_package_it->second.second.size()))
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::MALFORMED_DATAGRAM
+ , "Fragment number " + std::to_string(package_num) + " is invalid. Should be smaller than " + std::to_string(existing_package_it->second.second.size()));
+ return nullptr;
+ }
+
+ // Check if we already received this fragment
+ if (existing_package_it->second.second[package_num] != nullptr)
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::DUPLICATE_DATAGRAM
+ , "Fragment " + std::to_string(package_num) + " for package " + std::to_string(package_id));
+ return nullptr;
+ }
+
+ // Check if the size information from the fragment is valid
+ const uint32_t fragment_size = le32toh(header->len);
+ const unsigned int bytes_available = (static_cast(buffer->size()) - sizeof(ecaludp::v5::Header));
+ if (fragment_size > bytes_available)
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::MALFORMED_DATAGRAM
+ , "Faulty size of fragment. Should be " + std::to_string(fragment_size)
+ + ", but only " + std::to_string(bytes_available) + " bytes availabe.");
+ return nullptr;
+ }
+
+ // prepare a buffer view to the payload data and store the fragment in the list
+ const void* payload_data_ptr = static_cast(buffer->data()) + sizeof(ecaludp::v5::Header);
+ auto fragment_buffer_view = std::make_shared(payload_data_ptr, static_cast(fragment_size), buffer);
+ existing_package_it->second.second[package_num] = fragment_buffer_view;
+
+ // Increase the number of received fragments
+ existing_package_it->second.first.received_fragments_++;
+
+ // Set the last access time
+ existing_package_it->second.first.last_access_ = std::chrono::steady_clock::now();
+
+ // Maybe the message is already complete. So let's check and reassemble the
+ // package if necessary
+ return handle_fragmented_package_if_complete(existing_package_it, error);
+ }
+
+ std::shared_ptr Reassembly::handle_datagram_non_fragmented_message(const std::shared_ptr& buffer, ecaludp::Error& error)
+ {
+ const auto* header = reinterpret_cast(buffer->data());
+
+ // Check if the size information from the header is valid
+ const uint32_t payload_size = le32toh(header->len);
+ const unsigned int bytes_available = (static_cast(buffer->size()) - sizeof(ecaludp::v5::Header));
+
+ if (payload_size > bytes_available)
+ {
+ error = ecaludp::Error(ecaludp::Error::ErrorCode::MALFORMED_DATAGRAM
+ , "Faulty size of datagram. Should be " + std::to_string(payload_size)
+ + ", but only " + std::to_string(bytes_available) + " bytes availabe.");
+ return nullptr;
+ }
+
+ // Calculate the pointer to the payload data and create an OwningBuffer for that memory area
+ const void* payload_data_ptr = static_cast(buffer->data()) + sizeof(ecaludp::v5::Header);
+ auto payload_buffer = std::make_shared(payload_data_ptr, static_cast(payload_size), buffer);
+
+ error = ecaludp::Error::ErrorCode::OK;
+ return payload_buffer;
+ }
+
+ std::shared_ptr Reassembly::handle_fragmented_package_if_complete(const fragmented_package_map_t::const_iterator& it, ecaludp::Error& error)
+ {
+ // Check if we have a completed package
+ if (!(it->second.first.fragment_info_received_)
+ || !(it->second.first.received_fragments_ == it->second.first.total_fragments_))
+ {
+ error = ecaludp::Error::ErrorCode::OK;
+ return nullptr;
+ }
+
+ // Check if the package size is valid
+ {
+ size_t cummulated_package_sizes = 0;
+ for (const auto& fragment : it->second.second)
+ {
+ cummulated_package_sizes += fragment->size();
+ }
+
+ if (cummulated_package_sizes != it->second.first.total_size_bytes_)
+ {
+ // TODO: Add a performance option that doesn't build the long error string.
+ error = ecaludp::Error(Error::ErrorCode::MALFORMED_REASSEMBLED_MESSAGE
+ , "Size error. Should be " + std::to_string(it->second.first.total_size_bytes_)
+ + " bytes, but received " + std::to_string(cummulated_package_sizes) + "bytes.");
+
+ // Remove the package from the map. We don't need it anymore, as it is corrupted
+ fragmented_packages_.erase(it);
+
+ return nullptr;
+ }
+ }
+
+ // We have a complete package, so we can reassemble it
+ auto reassebled_buffer = reassemble_package(it);
+
+ // Remove the package from the map. We don't need it anymore, as it is complete
+ fragmented_packages_.erase(it);
+
+ // Return the package to the user
+ error = ecaludp::Error::ErrorCode::OK;
+ return reassebled_buffer;
+ }
+
+ std::shared_ptr Reassembly::reassemble_package(const fragmented_package_map_t::const_iterator& it)
+ {
+ // Create a mutable buffer that is big enough to hold the entire package
+ auto reassembled_buffer = largepackage_buffer_pool_.allocate();
+
+ reassembled_buffer->resize(it->second.first.total_size_bytes_);
+
+ void* current_pos = reassembled_buffer->data();
+
+ for (const auto& fragment : it->second.second)
+ {
+ // Copy the fragment into the reassembled buffer
+ memcpy(current_pos, fragment->data(), fragment->size());
+ current_pos = static_cast(current_pos) + fragment->size();
+ }
+
+ // In this case we don't have the header as residue in the raw memory, so we return the entire buffer.
+ return std::make_shared(reassembled_buffer->data(), reassembled_buffer->size(), reassembled_buffer);
+ }
+
+ void Reassembly::remove_old_packages(std::chrono::steady_clock::time_point max_age)
+ {
+ // Remove all packages that are older than max_age
+ for (auto it = fragmented_packages_.begin(); it != fragmented_packages_.end();)
+ {
+ if (it->second.first.last_access_ < max_age)
+ {
+ fragmented_packages_.erase(it++);
+ }
+ else
+ {
+ ++it;
+ }
+ }
+ }
+
+ }
+}
diff --git a/ecaludp/src/protocol/reassembly_v5.h b/ecaludp/src/protocol/reassembly_v5.h
new file mode 100644
index 0000000..389dafa
--- /dev/null
+++ b/ecaludp/src/protocol/reassembly_v5.h
@@ -0,0 +1,94 @@
+/********************************************************************************
+ * Copyright (c) 2024 Continental Corporation
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License, Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0.
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+#include
+#include