diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ae83e9a..11455cd 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,4 +1,4 @@ -name: CMake +name: Build Debug on: push: @@ -6,9 +6,12 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release + BUILD_TYPE: Debug jobs: build: @@ -40,9 +43,3 @@ jobs: # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test - working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} - diff --git a/.github/workflows/cmake_cppcheck.yml b/.github/workflows/cmake_cppcheck.yml new file mode 100644 index 0000000..6c75806 --- /dev/null +++ b/.github/workflows/cmake_cppcheck.yml @@ -0,0 +1,40 @@ +name: CppCheck +permissions: + contents: read + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # 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: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev cppcheck + + - uses: actions/checkout@v3 + + - 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 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: CppCheck + # Build your program with the given configuration + run: cppcheck --project=${{github.workspace}}/build/compile_commands.json --check-level=exhaustive --enable=all --inconclusive --force --inline-suppr --quiet + diff --git a/.github/workflows/cmake_debian_versions.yml b/.github/workflows/cmake_debian_versions.yml new file mode 100644 index 0000000..092d44c --- /dev/null +++ b/.github/workflows/cmake_debian_versions.yml @@ -0,0 +1,77 @@ +name: Build Debug (Debian Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Debian 12 (Bookworm) + image: debian:12 + version: "12" + - name: Debian 11 (Bullseye) + image: debian:11 + version: "11" + - name: Debian 10 (Buster) + image: debian:10 + version: "10" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "debian" ]]; then + case "${VERSION_ID}" in + 8*|9*|10*) + sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list + sed -i '/security.debian.org/d' /etc/apt/sources.list + if [[ -n "${VERSION_CODENAME:-}" ]]; then + printf '\ndeb http://archive.debian.org/debian-security %s/updates main contrib non-free\n' "${VERSION_CODENAME}" >> /etc/apt/sources.list + fi + printf "\n\nAcquire::Check-Valid-Until \"false\";\n" >> /etc/apt/apt.conf + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/cmake_fedora_versions.yml b/.github/workflows/cmake_fedora_versions.yml new file mode 100644 index 0000000..18d34ed --- /dev/null +++ b/.github/workflows/cmake_fedora_versions.yml @@ -0,0 +1,56 @@ +name: Build Debug (Fedora Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Fedora 41 + image: fedora:41 + - name: Fedora 40 + image: fedora:40 + - name: Fedora 39 + image: fedora:39 + + steps: + - name: Prepare environment + shell: bash + run: | + set -euo pipefail + dnf -y update + dnf -y install \ + cmake \ + gcc \ + gcc-c++ \ + make \ + git \ + pkgconf-pkg-config \ + can-utils \ + libsocketcan \ + libsocketcan-devel + dnf clean all + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/cmake_no_concepts.yml b/.github/workflows/cmake_no_concepts.yml new file mode 100644 index 0000000..439d76c --- /dev/null +++ b/.github/workflows/cmake_no_concepts.yml @@ -0,0 +1,45 @@ +name: Build Debug (C++11) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # 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: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + + - 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 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dsockcanpp_CONCEPT_SUPPORT=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + diff --git a/.github/workflows/cmake_ubuntu_versions.yml b/.github/workflows/cmake_ubuntu_versions.yml new file mode 100644 index 0000000..c4649ae --- /dev/null +++ b/.github/workflows/cmake_ubuntu_versions.yml @@ -0,0 +1,85 @@ +name: Build Debug (Ubuntu Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: CMake Ubuntu 25.04 + image: ubuntu:25.04 + version: "25.04" + - name: CMake Ubuntu 24.04 + image: ubuntu:24.04 + version: "24.04" + - name: CMake Ubuntu 23.04 + image: ubuntu:23.04 + version: "23.04" + - name: CMake Ubuntu 22.04 + image: ubuntu:22.04 + version: "22.04" + - name: CMake Ubuntu 21.04 + image: ubuntu:21.04 + version: "21.04" + - name: CMake Ubuntu 20.04 + image: ubuntu:20.04 + version: "20.04" + - name: CMake Ubuntu 19.04 + image: ubuntu:19.04 + version: "19.04" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "ubuntu" ]]; then + case "${VERSION_ID}" in + 19.04|21.04|23.04) + sed -i 's|http://archive.ubuntu.com/ubuntu/|http://old-releases.ubuntu.com/ubuntu/|g' /etc/apt/sources.list + sed -i 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' /etc/apt/sources.list + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..41af201 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,100 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ ] + schedule: + - cron: '34 4 * * 0' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: c-cpp + build-mode: autobuild + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/cpack.yml b/.github/workflows/cpack.yml new file mode 100644 index 0000000..b9f3c97 --- /dev/null +++ b/.github/workflows/cpack.yml @@ -0,0 +1,51 @@ +name: Build and Pack + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # 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: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + + - 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 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Pack + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: cpack . + diff --git a/.github/workflows/cpack_debian_versions.yml b/.github/workflows/cpack_debian_versions.yml new file mode 100644 index 0000000..5a65195 --- /dev/null +++ b/.github/workflows/cpack_debian_versions.yml @@ -0,0 +1,81 @@ +name: Build and Pack (Debian Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Release + +jobs: + build-and-pack: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Debian 12 (Bookworm) + image: debian:12 + version: "12" + - name: Debian 11 (Bullseye) + image: debian:11 + version: "11" + - name: Debian 10 (Buster) + image: debian:10 + version: "10" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "debian" ]]; then + case "${VERSION_ID}" in + 8*|9*|10*) + sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list + sed -i '/security.debian.org/d' /etc/apt/sources.list + if [[ -n "${VERSION_CODENAME:-}" ]]; then + printf '\ndeb http://archive.debian.org/debian-security %s/updates main contrib non-free\n' "${VERSION_CODENAME}" >> /etc/apt/sources.list + fi + echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99disable-check-valid-until + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget \ + rpm + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + cd ${{github.workspace}}/build + cpack . diff --git a/.github/workflows/cpack_fedora_versions.yml b/.github/workflows/cpack_fedora_versions.yml new file mode 100644 index 0000000..08a9286 --- /dev/null +++ b/.github/workflows/cpack_fedora_versions.yml @@ -0,0 +1,62 @@ +name: Build and Pack (Fedora Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Release + +jobs: + build-and-pack: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Fedora 41 + image: fedora:41 + - name: Fedora 40 + image: fedora:40 + - name: Fedora 39 + image: fedora:39 + + steps: + - name: Prepare environment + shell: bash + run: | + set -euo pipefail + dnf -y update + dnf -y install \ + cmake \ + gcc \ + gcc-c++ \ + make \ + git \ + pkgconf-pkg-config \ + can-utils \ + libsocketcan \ + libsocketcan-devel \ + wget \ + rpm-build + + dnf clean all + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + cd ${{github.workspace}}/build + cpack . diff --git a/.github/workflows/cpack_no_concepts.yml b/.github/workflows/cpack_no_concepts.yml new file mode 100644 index 0000000..d8b6208 --- /dev/null +++ b/.github/workflows/cpack_no_concepts.yml @@ -0,0 +1,51 @@ +name: Build and Pack (C++11) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # 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: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + + - 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 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dsockcanpp_CONCEPT_SUPPORT=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Pack + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: cpack . + diff --git a/.github/workflows/cpack_ubuntu_versions.yml b/.github/workflows/cpack_ubuntu_versions.yml new file mode 100644 index 0000000..70aff31 --- /dev/null +++ b/.github/workflows/cpack_ubuntu_versions.yml @@ -0,0 +1,86 @@ +name: Build and Pack (Ubuntu Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Release + +jobs: + build-and-pack: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: CPack Ubuntu 24.04 + image: ubuntu:24.04 + version: "24.04" + - name: CPack Ubuntu 23.04 + image: ubuntu:23.04 + version: "23.04" + - name: CPack Ubuntu 22.04 + image: ubuntu:22.04 + version: "22.04" + - name: CPack Ubuntu 21.04 + image: ubuntu:21.04 + version: "21.04" + - name: CPack Ubuntu 20.04 + image: ubuntu:20.04 + version: "20.04" + - name: CPack Ubuntu 19.04 + image: ubuntu:19.04 + version: "19.04" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "ubuntu" ]]; then + case "${VERSION_ID}" in + 19.04|21.04|23.04) + sed -i 's|http://archive.ubuntu.com/ubuntu/|http://old-releases.ubuntu.com/ubuntu/|g' /etc/apt/sources.list + sed -i 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' /etc/apt/sources.list + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget \ + rpm + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + cd ${{github.workspace}}/build + cpack . diff --git a/.github/workflows/gtest.yml b/.github/workflows/gtest.yml new file mode 100644 index 0000000..bddc5fe --- /dev/null +++ b/.github/workflows/gtest.yml @@ -0,0 +1,51 @@ +name: Build and Test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # 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: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt -y install can-utils libsocketcan-dev googletest + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + - uses: MarkusJx/googletest-installer@v1.1 + + - 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 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + diff --git a/.github/workflows/gtest_no_concepts.yml b/.github/workflows/gtest_no_concepts.yml new file mode 100644 index 0000000..099e146 --- /dev/null +++ b/.github/workflows/gtest_no_concepts.yml @@ -0,0 +1,51 @@ +name: Build and Test (C++11) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # 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: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt -y install can-utils libsocketcan-dev googletest + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + - uses: MarkusJx/googletest-installer@v1.1 + + - 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 -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -Dsockcanpp_CONCEPT_SUPPORT=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + diff --git a/.gitignore b/.gitignore index b3b2648..aad91be 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,19 @@ ### # Build directories ### -build/ +build*/ +_codeql_build_dir/ +_codeql_detected_source_root + +### +# Documentation (Doxygen) +### +html/ + +### +# cpack packages +### +packages/ ### # .vscode diff --git a/CMakeLists.txt b/CMakeLists.txt index 45af466..63f0f14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,30 +1,78 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.00) +project(sockcanpp LANGUAGES CXX VERSION 1.7.5 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") -option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF) +option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) +option(BUILD_TESTS "Build the tests" OFF) -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -include_directories( - include/ +option(sockcanpp_CONCEPT_SUPPORT "Allow higher level language support" ON) + +if (sockcanpp_CONCEPT_SUPPORT) + message(STATUS "Enabling support for higher level language features") + set(CMAKE_CXX_STANDARD 20) +else() + set(CMAKE_CXX_STANDARD 11) +endif() + +add_compile_options( + -Wall + -Werror + -Wpedantic + -Wno-unknown-pragmas ) -file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + add_link_options($<$:-s>) +endif() + +include(GNUInstallDirs) + +if (BUILD_SHARED_LIBS STREQUAL "ON") + add_library(${PROJECT_NAME}) + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) + set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +else() + add_library(${PROJECT_NAME} STATIC) +endif() -add_library(${PROJECT_NAME} ${FILES}) -set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) +### +# If BUILD_TESTS is set to ON, a static test library with the name of the project suffixed with "_test" will be created +### +if(BUILD_TESTS STREQUAL "ON") + enable_testing() + add_library(${PROJECT_NAME}_test STATIC) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/) +endif() -target_include_directories( - ${PROJECT_NAME} +add_subdirectory(include) +add_subdirectory(src) - INTERFACE include/ - INTERFACE include/exceptions +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + FILE_SET HEADERS ) -include(GNUInstallDirs) -install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) +install(EXPORT ${PROJECT_NAME}Targets + FILE "lib${PROJECT_NAME}Targets.cmake" + NAMESPACE ${PROJECT_NAME}:: + DESTINATION lib/cmake/${PROJECT_NAME} +) + +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "lib${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${${PROJECT_NAME}_VERSION} + COMPATIBILITY AnyNewerVersion +) + +install(FILES "cmake/lib${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION lib/cmake/${PROJECT_NAME}) + set(prefix ${CMAKE_INSTALL_PREFIX}) set(exec_prefix ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) @@ -34,3 +82,17 @@ set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) configure_file(libsockcanpp.pc.in ${CMAKE_BINARY_DIR}/libsockcanpp.pc @ONLY) # Install pkg-config files install(FILES ${CMAKE_BINARY_DIR}/libsockcanpp.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +include(cmake/cpack.cmake) + +### +# Docs target +### +add_custom_target("libsockcan_docs" COMMENT "Create Doxygen documentation") +add_custom_command( + TARGET "libsockcan_docs" + POST_BUILD + COMMENT "Generate Doxygen documentation for publication or reading" + COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) diff --git a/Doxyfile b/Doxyfile index e784ae3..6431cae 100644 --- a/Doxyfile +++ b/Doxyfile @@ -4,7 +4,7 @@ # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "Waveshare 8ch Relay Board Example" +PROJECT_NAME = "Libsockcanpp" PROJECT_NUMBER = PROJECT_BRIEF = "A complete C++ wrapper around socketcan." PROJECT_LOGO = diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..33f095f --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: SimonCahill diff --git a/README.md b/README.md index 24c6f6f..c1b43cc 100644 --- a/README.md +++ b/README.md @@ -13,19 +13,31 @@ libsockcanpp is a socketcan wrapper library for C++, aimed to be easy to use wit ## Building +> [!NOTE] +> **C++11** SUPPORT: +> This library supports modern C++ features, such as concepts in certain places. +> If your project cannot support C++20 features, you can force the C++ standard back by setting +> `-Dsockcanpp_CONCEPT_SUPPORT=OFF` in the command-line or `set(sockcanpp_CONCEPT_SUPPORT OFF CACHE BOOL "Force C++ standard back to 11")` +> in your CMakeLists.txt. + libsockcanpp was designed with use in CMake projects, but it can also easily be integrated into existing Makefile projects, as long as cmake is present on the build system. -1) clone the repository: `git clone https://github.com/Beatsleigher/libsockcanpp.git` +1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 2) create build directory: `mkdir build && cd build` 3) generate build files: `cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchains/desired-toolchain.cmake` 4) building library: `make -j` ## Incorporating into Cmake projects: -1) clone the repository: `git clone https://github.com/Beatsleigher/libsockcanpp.git` +### Git Clone + +1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 2) add the following to CMakeLists.txt ```cmake if (NOT TARGET sockcanpp) + # IF you need C++11 support: + # set(sockcanpp_CONCEPT_SUPPORT OFF CACHE BOOL "Force C++ standard back to 11") + add_subdirectory(/path/to/libsockcanpprepo ${CMAKE_CURRENT_BUILD_DIR}/libsockcanpp) endif() @@ -39,122 +51,137 @@ target_link_libraries( 3) generate and build 4) ??? profit -## Using the CAN driver +### CPM +If your project utilises CPM, you can also run -libsockcanpp provides a simple interface to socketcan, which is represented by the CanDriver class.
-`CanDriver` handles the setup of the socket; **it does not however setup the CAN interface!** - -### Instantiating CanDriver +```cmake +CPMAddPackage( + NAME sockcanpp + GIT_REPOSITORY https://github.com/SimonCahill/libsockcanpp.git + GIT_TAG master +) +``` -The example below describes how to instantiate a new instance of CanDriver.
-You can create as many instances as required, the resources are `free`'d once the instance has gone out of scope or been deleted. +# Using libsockcanpp -The following parameters are required for correct instantiation: +## First Steps -1) CAN interface: [v]can[0-?] -2) CAN protocol: see [linux/can.h](https://github.com/linux-can/can-utils/blob/master/include/linux/can.h) for options -3) (optional) CAN sender ID (arbitration ID) +In its current state, libsockcanpp doesn't support CANFD; very basic support exists for CANXL, although it is highly experimental and unstable. -The following exceptions may be thrown if something went wrong during initialisation: +This library provides a basic and straightforward API. +The main class is `CanDriver`; it is multi-instance and thread-safe. - - @see CanInitException - - if socketcan failed to initlialise - - if ioctl failed - - if socket binding failed +### Setting up a CAN Socket -CanDriver is fully thread-safe and can be used in multi-threaded applications. +> [!NOTE] +> This library does **NOT** setup the CAN interface. +> This can be done via the `ip` command: +> `$ sudo ip link set dev can0 up type can bitrate ` +> `$ sudo ip link set dev van0 up type vcan` ```cpp #include using sockcanpp::CanDriver; +using sockcanpp::CanMessage; +using sockcanpp::exceptions::CanException; int main() { - CanDriver canDriver("can0", CAN_RAW[, 0xbad]); - - return 0; -} -``` - -### Using CAN IDs -libsockcanpp provides a first-class datatype, @see CanId, which acts as an integer which can be either 11 or 29 bits in size.
-The @see CanId type is used through libsockcanpp to provide a semi fool-proof method of using CAN arbitration IDs without the pitfalls of using traditional 32-bit integers. + CanDriver cDriver{"can0", CAN_RAW}; // No default sender ID -CanId supports the following operations: - - bitwise AND/OR - - casting to: [u]int16, [u]int32 - - basic comparison: - - equal to - - not equal to - - greater than (or equal to) - - less than (or equal to) - - arithmetic: - - addition - - subtraction + sendCanFrame(cDriver); + sendMultipleFrames(cDriver); + receiveFrame(cDriver); -### Sending CAN frames + cDriver.setErrorFilter(); // Receive error frames -Sending CAN frames with sockcanpp is as easy as you could imagine. + filtermap_t canFilters{ + { 0x489, 0x7ff } // filter messages with 0x489 as ID + }; + cDriver.setCanFilters(canFilters); // Set X amount of CAN filters. See https://docs.kernel.org/networking/can.html#raw-protocol-sockets-with-can-filters-sock-raw -1) instantiate a @see CanDriver object -2) create a @see CanMessage -3) send message + cDriver.setCollectTelemetry(); // Enable telemetry collection, such as timestamps, data sent, etc. -```cpp -#include - -using sockcanpp::CanDriver; -using sockcanpp::CanId; -using sockcanpp::CanMessage; + cDriver.setReceiveOwnMessages(); // Echo sent frames back -void sendCanFrameExample() { - CanDriver canDriver("can0", CAN_RAW[, 0xd00d]); + cDriver.setReturnRelativeTimestamps(); // Enable relative timestamps - CanMessage messageToSend(0 /*send with default ID*/, "8 bytes!" /* the data */); +} +``` - auto sentByteCount = canDriver.sendMessage(messageToSend[, false /* don't force extended ID */]); +### Sending a single CAN frame - printf("Sent %d bytes via CAN!\n", sentByteCount); +```cpp +void sendCanFrame(CanDriver& driver) { + CanMessage msg{0x123, "\x01\x02\x03\x04\x05\x06\x07\x08"}; + driver.sendMessage(msg); + driver.sendMessage(msg, true); // force extended CAN frame } +``` -void sendMultipleFramesExample() { - CanDriver canDriver("can1", CAN_RAW[, 0 /* no default ID */]); +### Sending multiple CAN frames - queue messageQueue = { - CanMessage(0x269, "somedata"), - Canmessage(0x1e9, "moredata") +```cpp +void sendMultipleFrames(CanDriver& driver) { + vector messages{ + CanMessage{0x123, "\x01\x02\x03\x04\x05\x06\x07\x08"}, + CanMessage{0x456, "\x09\x10\x11\x12\x13\x14\x15\x16"} }; - auto sentByteCount = canDriver.sendMessageQueue(messageQueue[, milliseconds(20) /* delay between frames */[, false /* don't force extended ID */]]); - - printf("Sent %d bytes via CAN!\n", sentByteCount); + using namespace std::chrono::literals; + driver.sendMessageQueue(messages); // 20ms delay (Default) + driver.sendMessageQueue(messages, 20ns); // 20ns delay + driver.sendMessageQueue(messages, 20us, true); // 20microseconds delay | force extended } ``` -### Receiving messages via CAN - -Receiving CAN messages is almost as simple as sending them! Firstly, check if there are any messages in the buffer, then pull them out; either one-by-one, or all at once! +### Receiving a frame ```cpp -#include +void receiveFrame(CanDriver& driver) { + if (!driver.waitForMessages()) { return; } // No messages in buffer -using sockcanpp::CanDriver; -using sockcanpp::CanId; -using sockcanpp::CanMessage; + const auto receivedMsg = driver.readMessage(); + std::cout << receivedMsg << std::endl; // Outputs: CanMessage(canId: XXX, data: FF FF FF FF, timestampOffset: Nms) + + if (receivedMsg.isErrorFrame()) { + handleErrorFrame(receivedMsg); + } +} +``` -void receiveCanFramesExample() { - CanDriver canDriver("can2", CAN_RAW[, 0 /* no default ID */]); +### Handling an Error Frame - if (canDriver.waitForMessages([milliseconds(3000) /* timeout */])) { - // read a single message - CanMessage receivedMessage = canDriver.readMessage(); +libsockcanpp also provides features for evaluating errors states on the bus. +To enable this feature, `cDriver.setErrorFilter();` needs to be enabled. - // read all available messages - queue receivedMessages = canDriver.readQueuedMessages(); +When an error frame is received, the information is provided by the `CanMessage` class. - // handle CAN frames - } +```cpp +void handleErrorFrame(const CanMessage& msg) { + + // Handle as you see fit + + const auto hasBusError = msg.hasBusError(); + const auto hasBusOffError = msg.hasBusOffError(); + const auto hasControllerProblem = msg.hasControllerProblem(); + const auto hasControllerRestarted = msg.hasControllerRestarted(); + const auto hasErrorCounter = msg.hasErrorCounter(); + const auto hasLostArbitration = msg.hasLostArbitration(); + const auto hasProtocolViolation = msg.hasProtocolViolation(); + const auto hasTransceiverStatus = msg.hasTransceiverStatus(); + const auto missingAckOnTransmit = msg.missingAckOnTransmit(); + const auto isTxTimeout = msg.isTxTimeout(); + + const auto controllerError = msg.getControllerError(); + const auto protocolError = msg.getProtocolError(); + const auto transceiverError = msg.getTransceiverError(); + const auto txErrorCounter = msg.getTxErrorCounter(); + const auto rxErrorCounter = msg.getRxErrorCounter(); + const auto arbitrationLostInBit = msg.arbitrationLostInBit(); } ``` + +© 2020–2025 Simon Cahill — Licensed under Apache License 2.0 diff --git a/cmake/cpack.cmake b/cmake/cpack.cmake new file mode 100644 index 0000000..2a6c174 --- /dev/null +++ b/cmake/cpack.cmake @@ -0,0 +1,43 @@ +# these are cache variables, so they could be overwritten with -D, +set(CPACK_PACKAGE_NAME lib${PROJECT_NAME} + CACHE STRING "The resulting package name" +) + +# which is useful in case of packing only selected components instead of the whole thing +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A socketcan wrapper library for C++" + CACHE STRING "Package description for the package metadata" +) + +set(CPACK_VERBATIM_VARIABLES YES) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/packages") + +# https://unix.stackexchange.com/a/11552/254512 +#set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/some")#/${CMAKE_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_CONTACT "contact@simonc.eu") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Simon Cahill") + +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + +# package name for deb. If set, then instead of some-application-0.9.2-Linux.deb +# you'll get some-application_0.9.2_amd64.deb (note the underscores too) +set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + +# that is if you want every group to have its own package, +# although the same will happen if this is not set (so it defaults to ONE_PER_GROUP) +# and CPACK_DEB_COMPONENT_INSTALL is set to YES +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP) + +# without this you won't be able to pack only specified component +set(CPACK_DEB_COMPONENT_INSTALL YES) + +set(CPACK_GENERATOR TGZ DEB TZ STGZ RPM ZIP) + +include(CPack) \ No newline at end of file diff --git a/cmake/libsockcanppConfig.cmake b/cmake/libsockcanppConfig.cmake new file mode 100644 index 0000000..dbee823 --- /dev/null +++ b/cmake/libsockcanppConfig.cmake @@ -0,0 +1,3 @@ +# include(CMakeFindDependencyMacro) +# find_dependency(xxx 2.0) +include(${CMAKE_CURRENT_LIST_DIR}/libsockcanppTargets.cmake) \ No newline at end of file diff --git a/doxygen-awesome-css b/doxygen-awesome-css index 9380569..df88fe4 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit 9380569e8aea36374d848c8a43c6f9c6343d9bc0 +Subproject commit df88fe4fdd97714fadfd3ef17de0b4401f804052 diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..2ae47b1 --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.23) + +target_sources(${PROJECT_NAME} + PUBLIC FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR} + FILES + CanDriver.hpp + CanId.hpp + CanMessage.hpp + exceptions/CanCloseException.hpp + exceptions/CanException.hpp + exceptions/CanInitException.hpp + exceptions/InvalidSocketException.hpp + can_errors/CanControllerError.hpp + can_errors/CanProtocolError.hpp + can_errors/CanTransceiverError.hpp +) + +if (TARGET sockcanpp_test) + target_sources(sockcanpp_test + PUBLIC FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR} + FILES + CanDriver.hpp + CanId.hpp + CanMessage.hpp + exceptions/CanCloseException.hpp + exceptions/CanException.hpp + exceptions/CanInitException.hpp + exceptions/InvalidSocketException.hpp + can_errors/CanControllerError.hpp + can_errors/CanProtocolError.hpp + can_errors/CanTransceiverError.hpp + ) +endif() diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index f86c7f5..b4aca1a 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -1,13 +1,11 @@ /** * @file CanDriver.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the declarations for the SocketCAN wrapper in C++. * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,10 +26,16 @@ ////////////////////////////// // SYSTEM INCLUDES // ////////////////////////////// +#include #include #include #include #include +#include + +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L ////////////////////////////// // LOCAL INCLUDES // @@ -39,6 +43,10 @@ #include "CanId.hpp" #include "CanMessage.hpp" +#if __cplusplus < 201703L +#include "TlOptional.hpp" +#endif // __cplusplus < 201703L + /** * @brief Main library namespace. * @@ -46,10 +54,26 @@ */ namespace sockcanpp { + #if __cplusplus >= 201300 + using namespace std::chrono_literals; + #endif // __cplusplus >= 201300 + + #if __cplusplus >= 201703L + using std::optional; + #else + using tl::optional; + #endif // __cplusplus < 201703L + + using std::atomic; + using std::chrono::microseconds; using std::chrono::milliseconds; + using std::chrono::nanoseconds; using std::mutex; using std::string; using std::queue; + using std::unordered_map; + + using filtermap_t = unordered_map; /** * @brief CanDriver class; handles communication via CAN. @@ -61,61 +85,109 @@ namespace sockcanpp { */ class CanDriver { public: // +++ Static +++ - static const int32_t CAN_MAX_DATA_LENGTH; ///!< The maximum amount of bytes allowed in a single CAN frame - static const int32_t CAN_SOCK_RAW; ///!< The raw CAN protocol - static const int32_t CAN_SOCK_SEVEN; ///!< A separate CAN protocol, used by certain embedded device OEMs. + static constexpr int32_t CAN_MAX_DATA_LENGTH = 8; //!< The maximum amount of bytes allowed in a single CAN frame + static constexpr int32_t CAN_SOCK_RAW = CAN_RAW; //!< The raw CAN protocol + static constexpr int32_t CAN_SOCK_SEVEN = 7; //!< A separate CAN protocol, used by certain embedded device OEMs. public: // +++ Constructor / Destructor +++ - CanDriver(const string canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); ///!< Constructor - CanDriver(const string canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); - CanDriver() {} - virtual ~CanDriver() { uninitialiseSocketCan(); } ///!< Destructor + CanDriver(const string& canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); //!< Constructor + CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); + CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId = 0); + CanDriver() = default; + virtual ~CanDriver() { uninitialiseSocketCanNoThrow(); } //!< Destructor public: // +++ Getter / Setter +++ - CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } ///!< Sets the default sender ID + CanDriver& setDefaultSenderId(const CanId& id) { this->m_defaultSenderId = id; return *this; } //!< Sets the default sender ID - const CanId getDefaultSenderId() const { return this->_defaultSenderId; } ///!< Gets the default sender ID + CanId getDefaultSenderId() const { return this->m_defaultSenderId; } //!< Gets the default sender ID - const int32_t getFilterMask() const { return this->_canFilterMask; } ///!< Gets the filter mask used by this instance - const int32_t getMessageQueueSize() const { return this->_queueSize; } ///!< Gets the amount of CAN messages found after last calling waitForMessages() - const int32_t getSocketFd() const { return this->_socketFd; } ///!< The socket file descriptor used by this instance. - - public: // +++ I/O +++ - virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); ///!< Waits for CAN messages to appear + filtermap_t getFilterMask() const { return this->m_canFilterMask; } //!< Gets the filter mask used by this instance - virtual CanMessage readMessage(); ///!< Attempts to read a single message from the bus + int32_t getMessageQueueSize() const { return this->m_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() + int32_t getSocketFd() const { return this->m_socketFd; } //!< The socket file descriptor used by this instance. - virtual int32_t sendMessage(const CanMessage message, bool forceExtended = false); ///!< Attempts to send a single CAN message - virtual int32_t sendMessageQueue(queue messages, - milliseconds delay = milliseconds(20), bool forceExtended = false); ///!< Attempts to send a queue of messages - - virtual queue readQueuedMessages(); ///!< Attempts to read all queued messages from the bus + size_t getReceivedMessageCount() const { return this->m_receivedMessages.load(); } //!< The number of messages received by this instance. - virtual void setCanFilterMask(const int32_t mask); ///!< Attempts to set a new CAN filter mask to the BIOS - - protected: // +++ Socket Management +++ - virtual void initialiseSocketCan(); ///!< Initialises socketcan - virtual void uninitialiseSocketCan(); ///!< Uninitialises socketcan - - private: - virtual CanMessage readMessageLock(bool const lock = true); ///!< readMessage deadlock guard - CanId _defaultSenderId; ///!< The ID to send messages with if no other ID was set. - - int32_t _canFilterMask; ///!< The bit mask used to filter CAN messages - int32_t _canProtocol; ///!< The protocol used when communicating via CAN - int32_t _socketFd; ///!< The CAN socket file descriptor - int32_t _queueSize; ////!< The size of the message queue read by waitForMessages() - - ///!< Mutex for thread-safety. - mutex _lock; - mutex _lockSend; - - string _canInterface; ///!< The CAN interface used for communication (e.g. can0, can1, ...) + string getCanInterface() const { return this->m_canInterface; } //!< The CAN interface used by this instance. + public: // +++ I/O +++ + #if __cplusplus >= 201300 + #define sockcanpp_3KUS 3000us + #define sockcanpp_3KMS 3000ms + #define sockcanpp_3KNS 3000ns + + #define sockcanpp_20US 20us + #define sockcanpp_20MS 20ms + #define sockcanpp_20NS 20ns + #else // C++11 + #define sockcanpp_3KUS std::chrono::microseconds(3000) + #define sockcanpp_3KMS std::chrono::milliseconds(3000) + #define sockcanpp_3KNS std::chrono::nanoseconds(3000) + + #define sockcanpp_20US std::chrono::microseconds(20) + #define sockcanpp_20MS std::chrono::milliseconds(20) + #define sockcanpp_20NS std::chrono::nanoseconds(20) + #endif // __cplusplus >= 201300 + + virtual bool waitForMessages(microseconds timeout = sockcanpp_3KUS); //!< Waits for CAN messages to appear + virtual bool waitForMessages(milliseconds timeout = sockcanpp_3KMS); //!< Waits for CAN messages to appear + virtual bool waitForMessages(nanoseconds timeout = sockcanpp_3KNS); //!< Waits for CAN messages to appear + + virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus + + virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message + virtual ssize_t sendMessageQueue(queue& messages, microseconds delay, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, milliseconds delay = sockcanpp_20MS, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay, bool forceExtended = false); //!< Attempts to send a queue of messages + + virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus + + public: // +++ Socket Management +++ + virtual void allowCanFdFrames(const bool enabled = true) const; //!< Sets the CAN FD frame option for the interface + #ifdef CANXL_XLF + virtual void allowCanXlFrames(const bool enabled = true) const; //!< Sets the CAN XL frame option for the interface + #endif // CANXL_XLF + virtual void joinCanFilters() const; //!< Configures the socket to join the CAN filters + virtual void setCanFilterMask(const int32_t mask, const CanId& filterId); //!< Attempts to set a new CAN filter mask to the interface + virtual void setCanFilters(const filtermap_t& filters); //!< Sets the CAN filters for the interface + virtual void setCollectTelemetry(const bool enabled = true); //!< Sets the telemetry collection option for the interface + virtual void setErrorFilter(const bool enabled = true) const; //!< Sets the error filter for the interface + virtual void setReceiveOwnMessages(const bool enabled = true) const; //!< Sets the receive own messages option for the interface + virtual void setReturnRelativeTimestamps(const bool enabled = true) { m_relativeTimestamps = enabled; } + + private: // +++ Socket Management +++ + void initialiseSocketCan(); //!< Initialises socketcan + void uninitialiseSocketCan(); //!< Uninitialises socketcan + void uninitialiseSocketCanNoThrow() noexcept; //!< Uninitialises socketcan without throwing exceptions + + private: // +++ Member Functions +++ + virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard + virtual milliseconds readFrameTimestamp(); + + private: // +++ Variables +++ + atomic m_receivedMessages{0}; //!< The number of received messages + + bool m_canReadQueueSize{true}; //!< Is the queue size available + bool m_collectTelemetry{false}; //!< Whether or not to collect telemetry data from the CAN bus + bool m_relativeTimestamps{false}; //!< Whether or not to use relative timestamps + + CanId m_defaultSenderId; //!< The ID to send messages with if no other ID was set. + + filtermap_t m_canFilterMask; //!< The bit mask used to filter CAN messages + + int32_t m_canProtocol{CAN_RAW}; //!< The protocol used when communicating via CAN + int32_t m_socketFd{-1}; //!< The CAN socket file descriptor + int32_t m_queueSize{0}; //!< The size of the message queue read by waitForMessages() + + optional m_firstTimestamp{}; + + mutex m_lock{}; //!< Mutex for thread-safety. + mutex m_lockSend{}; + + string m_canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) + }; - - /** * @brief Formats a std string object. * @@ -130,7 +202,7 @@ namespace sockcanpp { template string formatString(const string& format, Args... args) { using std::unique_ptr; - auto stringSize = snprintf(NULL, 0, format.c_str(), args...) + 1; // +1 for \0 + auto stringSize = snprintf(nullptr, 0, format.c_str(), args...) + 1; // +1 for \0 unique_ptr buffer(new char[stringSize]); snprintf(buffer.get(), stringSize, format.c_str(), args...); diff --git a/include/CanId.hpp b/include/CanId.hpp index 92d51df..f5977e1 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -1,13 +1,11 @@ /** * @file CanId.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a value-type representing a CAN identifier. * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 Simon Cahill - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,18 +20,50 @@ * limitations under the License. */ -#ifndef LIBSOCKPP_INCLUDE_CANID_HPP -#define LIBSOCKPP_INCLUDE_CANID_HPP +#ifndef LIBSOCKCANPP_INCLUDE_CANID_HPP +#define LIBSOCKCANPP_INCLUDE_CANID_HPP ////////////////////////////// // SYSTEM INCLUDES // ////////////////////////////// +// stl +#include #include #include +#include #include -#include +#include #include +// libc +#include +#include + +#if __cpp_concepts >= 201907 +template +concept Stringable = requires(Str s) { { s.data() + s.size() } -> std::convertible_to; }; + +template +concept CChar = requires(CharArr c) { std::is_same_v; }; + +template +concept Integral = requires(Int i) { std::is_integral_v; }; + +template +concept ConvertibleToCanId = Stringable || Integral || CChar; +#endif + +#ifndef CAN_ERR_CNT +/** + * Fallback definition for CAN_ERR_CNT. + * The value 0x00000200U is defined by the Linux CAN subsystem (see ). + * This fallback is provided in case the system headers do not define CAN_ERR_CNT, + * such as when building on non-Linux systems or with older kernel headers. + * See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/can/error.h + */ +#define CAN_ERR_CNT 0x00000200U +#endif // CAN_ERR_CNT + namespace sockcanpp { using std::bitset; @@ -47,124 +77,164 @@ namespace sockcanpp { */ struct CanId { public: // +++ Constructors +++ - CanId(const CanId& orig): _identifier(orig._identifier), _isErrorFrame(orig._isErrorFrame), - _isRemoteTransmissionRequest(orig._isRemoteTransmissionRequest), _isStandardFrameId(orig._isStandardFrameId), - _isExtendedFrameId(orig._isExtendedFrameId) { /* copy */ } - - CanId(const uint32_t identifier): _identifier(identifier) { - // TODO: Switch to using bitmasks! - - if (isValidIdentifier(identifier)) { - if (((int32_t)log2(identifier) + 1) < 11) { - _isStandardFrameId = true; - } else { _isExtendedFrameId = true; } - } else if (isErrorFrame(identifier)) { - _isErrorFrame = true; - } else if (isRemoteTransmissionRequest(identifier)) { - _isRemoteTransmissionRequest = true; - } - } + constexpr CanId() = default; + constexpr CanId(const canid_t id): m_identifier(id) { } + constexpr CanId(const int32_t id): m_identifier(id) { } - CanId(): _identifier(0), _isStandardFrameId(true) { } + #if __cpp_concepts >= 201907 + template + CanId(const T& id) { m_identifier = std::stoul(id.data(), nullptr, 16); } + #endif // __cpp_concepts >= 201907 + + CanId(const char* id) { m_identifier = std::stoul(id, nullptr, 16); } public: // +++ Operators +++ + constexpr canid_t operator *() const { return m_identifier; } //!< Returns the raw CAN ID value. + + friend std::ostream& operator <<(std::ostream& os, const CanId& id) { + os << std::hex << id.m_identifier; + return os; + } //!< Outputs the CanId to a stream in hexadecimal format. -#pragma region "Implicit Cast Operators" - operator int16_t() const { return isStandardFrameId() ? (int16_t)_identifier : throw system_error(error_code(0xbad1d, generic_category()), "INVALID CAST: ID is extended or invalid!"); } - operator uint16_t() const { return isStandardFrameId() ? (uint16_t)_identifier : throw system_error(error_code(0xbad1d, generic_category()), "INVALID CAST: ID is extended or invalid!"); } - operator int32_t() const { return _identifier; } - operator uint32_t() const { return _identifier; } +#pragma region "Conversions" + constexpr operator int16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } + constexpr operator uint16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } + constexpr operator int32_t() const { return m_identifier & CAN_ERR_MASK; } + constexpr operator canid_t() const { return m_identifier & CAN_ERR_MASK; } #pragma endregion #pragma region "Bitwise Operators" - CanId operator &(CanId& x) const { return _identifier & x._identifier; } - CanId operator &(const CanId x) const { return _identifier & x._identifier; } - CanId operator &(const int16_t x) const { return _identifier & x; } - CanId operator &(const uint16_t x) const { return _identifier & x; } - CanId operator &(const int32_t x) const { return _identifier & x; } - CanId operator &(const uint32_t x) const { return _identifier & x; } - CanId operator &(const int64_t x) const { return _identifier & x; } - CanId operator &(const uint64_t x) const { return _identifier & x; } - - CanId operator |(CanId& x) const { return _identifier | x._identifier; } - CanId operator |(const CanId x) const { return _identifier | x._identifier; } - CanId operator |(const int16_t x) const { return _identifier | x; } - CanId operator |(const uint16_t x) const { return _identifier | x; } - CanId operator |(const int32_t x) const { return _identifier | x; } - CanId operator |(const uint32_t x) const { return _identifier | x; } - CanId operator |(const int64_t x) const { return _identifier | x; } - CanId operator |(const uint64_t x) const { return _identifier | x; } + template + constexpr CanId operator &(const T x) const { return m_identifier & x; } //!< Performs a bitwise AND operation on this ID and another. + constexpr CanId operator &(const CanId& x) const { return m_identifier & x.m_identifier; } //!< Performs a bitwise AND operation on this ID and another. + + template + constexpr CanId operator |(const T x) const { return m_identifier | x; } //!< Performs a bitwise OR operation on this ID and a 16-bit integer. + constexpr CanId operator |(const CanId& x) const { return m_identifier | x.m_identifier; } //!< Performs a bitwise OR operation on this ID and another. + + template + constexpr CanId operator ^(const T x) const { return m_identifier ^ x; } //!< Performs a bitwise XOR operation on this ID and a 16-bit integer. + constexpr CanId operator ^(const CanId& x) const { return m_identifier ^ x.m_identifier; } //!< Performs a bitwise XOR operation on this ID and another. + + constexpr CanId operator ~() const { return ~m_identifier; } //!< Performs a bitwise NOT operation on this ID. + + template + constexpr CanId operator <<(const T x) const { return m_identifier << x; } //!< Shifts this ID to the left by a 16-bit integer. + constexpr CanId operator <<(const CanId& x) const { return m_identifier << x.m_identifier; } //!< Shifts this ID to the left by another. + + template + constexpr CanId operator >>(const T x) const { return m_identifier >> x; } //!< Shifts this ID to the right by a 16-bit integer. + constexpr CanId operator >>(const CanId& x) const { return m_identifier >> x.m_identifier; } //!< Shifts this ID to the right by another. + + template + CanId operator <<=(const T x) { return m_identifier <<= x; } //!< Shifts this ID to the left by a 16-bit integer. + CanId operator <<=(const CanId& x) { return m_identifier <<= x.m_identifier; } //!< Shifts this ID to the left by another. + + template + CanId operator >>=(const T x) { return m_identifier >>= x; } //!< Shifts this ID to the right by a 16-bit integer. + CanId operator >>=(const CanId& x) { return m_identifier >>= x.m_identifier; } //!< Shifts this ID to the right by another. + #pragma endregion #pragma region "Comparison Operators" - bool operator ==(CanId& x) const { return _identifier == x._identifier; } - bool operator ==(const CanId& x) const { return _identifier == x._identifier; } - bool operator ==(const int16_t x) const { return _identifier == x; } - bool operator ==(const uint16_t x) const { return _identifier == x; } - bool operator ==(const int32_t x) const { return _identifier == x; } - bool operator ==(const uint32_t x) const { return _identifier == x; } - bool operator ==(const int64_t x) const { return (x > UINT32_MAX || x < INT32_MIN) ? false : x == _identifier; } - bool operator ==(const uint64_t x) const { return x > UINT32_MAX ? false : x == _identifier; } - bool operator !=(CanId& x) const { return _identifier != x._identifier; } - bool operator !=(const CanId& x) const { return _identifier != x._identifier; } - bool operator !=(const int16_t x) const { return _identifier != x; } - bool operator !=(const uint16_t x) const { return _identifier != x; } - bool operator !=(const int32_t x) const { return _identifier != x; } - bool operator !=(const uint32_t x) const { return _identifier != x; } - bool operator !=(const int64_t x) const { return (x > UINT32_MAX || x < INT32_MIN) ? false : x != _identifier; } - bool operator !=(const uint64_t x) const { return x > UINT32_MAX ? false : x != _identifier; } - - bool operator <(CanId& x) const { return x._identifier < _identifier; } - bool operator <(int32_t x) const { return x < _identifier; } - bool operator <(uint32_t x) const { return x < _identifier; } - bool operator <(int16_t x) const { return x < _identifier; } - bool operator <(uint16_t x) const { return x < _identifier; } - bool operator <=(CanId& x) const { return x._identifier <= _identifier; } - bool operator >(CanId& x) const { return x._identifier > _identifier; } - bool operator >(int32_t x) const { return x > _identifier; } - bool operator >(uint32_t x) const { return x > _identifier; } - bool operator >(int16_t x) const { return x > _identifier; } - bool operator >(uint16_t x) const { return x > _identifier; } - bool operator >=(CanId& x) const { return x._identifier >= _identifier; } - bool operator <(const CanId& x) const { return x._identifier < _identifier; } - bool operator <=(const CanId& x) const { return x._identifier <= _identifier; } - bool operator >(const CanId& x) const { return x._identifier > _identifier; } - bool operator >=(const CanId& x) const { return x._identifier >= _identifier; } + constexpr bool operator ==(const CanId& x) const { return m_identifier == x.m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator ==(const T x) const { return m_identifier == static_cast(x); } //!< Compares this ID to another. + + constexpr bool operator !=(const CanId& x) const { return m_identifier != x.m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator !=(const T x) const { return m_identifier != static_cast(x); } //!< Compares this ID to another. + + template + constexpr bool operator <(T x) const { return static_cast(x) < m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator >(T x) const { return static_cast(x) > m_identifier; } //!< Compares this ID to a 32-bit integer. + + template + constexpr bool operator <=(const T x) const { return m_identifier <= static_cast(x); } //!< Compares this ID to another. + + template + constexpr bool operator >=(const T x) const { return m_identifier >= static_cast(x); } //!< Compares this ID to another. #pragma endregion #pragma region "Assignment Operators" - CanId operator =(const int32_t val) { - uint32_t tmpVal = val; - auto tmp = (isValidIdentifier(tmpVal) ? CanId(val) : throw system_error(error_code(0x5421, generic_category()), "INVALID CAST: ID is extended or invalid!")); - return tmp; - } + #if __cplusplus >= 201703L + template + constexpr CanId& operator =(const T val) { m_identifier = val; return *this; } //!< Assigns a new integer to this CanID + #else + template + CanId& operator =(const T val) { + m_identifier = static_cast(val); + return *this; + } //!< Assigns a new integer to this CanID + #endif // __cplusplus >= 201703L - CanId operator =(const uint32_t val) { - uint32_t tmp = val; - return (isValidIdentifier(tmp) ? CanId(val) : throw system_error(error_code(0x5421, generic_category()), "INVALID CAST: ID is extended or invalid!")); + #if __cpp_concepts >= 201907 + template + CanId& operator =(const T& val) { + return operator=(std::stoul(val.data(), nullptr, 16)); } + #endif // __cpp_concepts >= 201907 - CanId operator =(const int64_t val) { return operator =((int32_t)val); } + + #if __cplusplus >= 201703L + constexpr CanId& operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. + + template + constexpr CanId& operator |=(const T x) { m_identifier |= x; return *this; } //!< Performs a bitwise OR operation on this ID and another. + + template + constexpr CanId& operator &=(const T x) { m_identifier &= x; return *this; } //!< Performs a bitwise AND operation on this ID and another. + + template + constexpr CanId& operator ^=(const T x) { m_identifier ^= x; return *this; } //!< Performs a bitwise XOR operation on this ID and another. + #else + CanId& operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. + + template + CanId& operator |=(const T x) { m_identifier |= x; return *this; } //!< Performs a bitwise OR operation on this ID and another. + + template + CanId& operator &=(const T x) { m_identifier &= x; return *this; } //!< Performs a bitwise AND operation on this ID and another. + + template + CanId& operator ^=(const T x) { m_identifier ^= x; return *this; } //!< Performs a bitwise XOR operation on this ID and another. + #endif // __cplusplus >= 201703L #pragma endregion #pragma region "Arithmetic Operators" - CanId operator +(CanId& x) const { return _identifier + x._identifier; } - CanId operator +(const CanId& x) const { return _identifier + x._identifier; } - CanId operator +(const int16_t x) const { return _identifier + x; } - CanId operator +(const uint16_t x) const { return _identifier + x; } - CanId operator +(const int32_t x) const { return _identifier + x; } - CanId operator +(const uint32_t x) const { return _identifier + x; } - CanId operator +(const int64_t x) const { return _identifier + x; } - CanId operator +(const uint64_t x) const { return _identifier + x; } - - CanId operator -(CanId& x) const { return _identifier - x._identifier; } - CanId operator -(const CanId& x) const { return _identifier - x._identifier; } - CanId operator -(const int16_t x) const { return _identifier - x; } - CanId operator -(const uint16_t x) const { return _identifier - x; } - CanId operator -(const int32_t x) const { return _identifier - x; } - CanId operator -(const uint32_t x) const { return _identifier - x; } - CanId operator -(const int64_t x) const { return _identifier - x; } - CanId operator -(const uint64_t x) const { return _identifier - x; } + template + constexpr CanId operator +(const T x) const { return m_identifier + x; } + + template + constexpr CanId operator +=(const T x) { return m_identifier += x; } + + template + constexpr CanId operator -=(const T x) { return m_identifier -= x; } + + template + constexpr CanId operator -(const T x) const { return m_identifier - x; } + + template + constexpr CanId operator *(const T x) const { return m_identifier * x; } + + template + constexpr CanId operator *= (const T x) { return m_identifier *= x; } + + template + constexpr CanId operator /(const T x) const { return m_identifier / x; } + + template + constexpr CanId operator /=(const T x) { return m_identifier /= x; } + + template + constexpr CanId operator %(const T x) const { return m_identifier % x; } + + template + constexpr CanId operator %=(const T x) { return m_identifier %= x; } #pragma endregion public: // +++ Validity Checks +++ @@ -176,16 +246,9 @@ namespace sockcanpp { * @return true If value is a valid CAN identifier. * @return false Otherwise. */ - static bool isValidIdentifier(uint32_t value) { - int32_t tmpValue = ((int32_t)log2(value) + 2); // Get bit count - - // Check for extended frame flag - if (tmpValue >= 29) { - value = (value & CAN_EFF_FLAG) ? (value & CAN_EFF_MASK) : (value & CAN_SFF_MASK); - tmpValue = ((int32_t)log2(value) + 1); // Get bit count again - } - - return (value == 0) /* Default value, also valid ID */ || ((tmpValue <= 29 && tmpValue > 0)); + template + static constexpr bool isValidIdentifier(T value) { + return static_cast(value) <= CAN_EFF_MASK; } /** @@ -196,9 +259,9 @@ namespace sockcanpp { * @return true If value has the error frame flag (bit) set to 1. * @return false Otherwise. */ - static bool isErrorFrame(uint32_t value) { - try { return bitset(value).test(29); } - catch (...) { return false; /* Brute-force, but works. */ } + template + static constexpr bool isErrorFrame(T value) { + return static_cast(value) & CAN_ERR_FLAG; } /** @@ -209,29 +272,57 @@ namespace sockcanpp { * @return true If the frame is a remote transmission request. * @return false Otherwise. */ - static bool isRemoteTransmissionRequest(uint32_t value) { - try { return bitset(value).test(30); } - catch (...) { return false; /* Brute-force, but works. */ } + template + static constexpr bool isRemoteTransmissionRequest(T value) { + return static_cast(value) & CAN_RTR_FLAG; + } + + /** + * @brief Indicates whether or not a given integer is an extended frame ID. + * + * @param value The integer to check. + * + * @return true If the frame is in the extended format. + * @return false Otherwise. + */ + template + static constexpr bool isExtendedFrame(T value) { + return static_cast(value) & CAN_EFF_FLAG; } public: // +++ Getters +++ - bool hasErrorFrameFlag() const { return _isErrorFrame; } - bool hasRtrFrameFlag() const { return _isRemoteTransmissionRequest; } - bool isStandardFrameId() const { return _isStandardFrameId; } - bool isExtendedFrameId() const { return _isExtendedFrameId; } + constexpr bool hasErrorFrameFlag() const { return isErrorFrame(m_identifier); } //!< Indicates whether or not this ID is an error frame. + constexpr bool hasRtrFrameFlag() const { return isRemoteTransmissionRequest(m_identifier); } //!< Indicates whether or not this ID is a remote transmission request. + constexpr bool isStandardFrameId() const { return !isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is a standard frame ID. + constexpr bool isExtendedFrameId() const { return isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is an extended frame ID. public: // +++ Equality Checks +++ - bool equals(CanId otherId) const { return *this == otherId; } + constexpr bool equals(const CanId& otherId) const { return m_identifier == otherId.m_identifier; } //!< Compares this ID to another. + + public: // +++ Error Frame Handling +++ + constexpr bool hasBusError() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_BUSERROR); } //!< Checks if this ID has a bus error. + constexpr bool hasBusOffError() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_BUSOFF); } //!< Checks if this ID has a bus-off error. + constexpr bool hasControllerProblem() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_CRTL); } //!< Checks if this ID has a controller problem. + constexpr bool hasControllerRestarted() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_RESTARTED); } //!< Checks if this ID has a controller restarted error. + constexpr bool hasErrorCounter() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_CNT); } //!< Checks if this ID has an error counter. + constexpr bool hasLostArbitration() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_LOSTARB); } //!< Checks if this ID has lost arbitration. + constexpr bool hasProtocolViolation() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_PROT); } //!< Checks if this ID has a protocol violation. + constexpr bool hasTransceiverStatus() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_TRX); } //!< Checks if this ID has a transceiver status error. + constexpr bool missingAckOnTransmit() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_ACK); } //!< Checks if this ID is missing an ACK on transmit. + constexpr bool isTxTimeout() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_TX_TIMEOUT); } //!< Checks if this ID is a transmission timeout error frame. private: // +++ Variables +++ - bool _isErrorFrame = false; - bool _isRemoteTransmissionRequest = false; - bool _isStandardFrameId = false; - bool _isExtendedFrameId = false; + uint32_t m_identifier = 0; + }; - uint32_t _identifier = 0; + /** + * @brief Implements a hash function for the CanId type. + */ + struct CanIdHasher { + public: + size_t operator()(const CanId& id) const { return std::hash()(*id); } }; } -#endif // LIBSOCKPP_INCLUDE_CANID_HPP +#endif // LIBSOCKCANPP_INCLUDE_CANID_HPP diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index dbf0ff3..aab2fd7 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -1,13 +1,11 @@ /** * @file CanMessage.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a CAN message representation in C++. * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 Simon Cahill - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,8 +28,14 @@ ////////////////////////////// #include +#if __cpp_lib_bit_cast >= 201806L +#include +#endif // __cpp_lib_bit_cast >= 201806L #include #include +#if __cpp_lib_span >= 201907L +#include +#endif // __cpp_lib_span >= 201907L #include #include #include @@ -40,51 +44,172 @@ // LOCAL INCLUDES // ////////////////////////////// #include "CanId.hpp" +#include "can_errors/CanControllerError.hpp" +#include "can_errors/CanProtocolError.hpp" +#include "can_errors/CanTransceiverError.hpp" namespace sockcanpp { + #if __cpp_lib_bit_cast >= 201806L + # define type_cast std::bit_cast + #else + # define type_cast reinterpret_cast + #endif // __cpp_lib_bit_cast >= 201806L + + using std::chrono::milliseconds; using std::error_code; using std::generic_category; - using std::memcpy; using std::string; using std::system_error; + #if __cpp_lib_string_view >= 201803L + using std::string_view; //!< Use string_view if available, otherwise use string. + #endif // __cpp_lib_string_view + + #if __cplusplus >= 201703L + #define NO_DISCARD [[nodiscard]] + #else + #define NO_DISCARD + #endif // __cplusplus >= 201703L + /** * @brief Represents a CAN message that was received. */ - class CanMessage { - public: // +++ Constructor / Destructor +++ - CanMessage(const struct can_frame frame): - _canIdentifier(frame.can_id), _frameData((const char*)frame.data, frame.can_dlc), _rawFrame(frame) { } + template + class CanMessageT { + static_assert(std::is_trivially_copyable::value, "can_frame must be trivially copyable"); //!< Ensures that the can_frame structure is trivially copyable, which is necessary for efficient copying and moving of CAN messages. - CanMessage(const CanId canId, const string frameData): _canIdentifier(canId), _frameData(frameData) { - if (frameData.size() > 8) { - throw system_error(error_code(0xbadd1c, generic_category()), "Payload too big!"); + public: // +++ Constructor / Destructor +++ + CanMessageT() = default; //!< Default constructor, initializes an empty CAN message. + + explicit CanMessageT(const struct can_frame& frame): m_canIdentifier(frame.can_id), m_rawFrame(frame) { } //!< Constructs a CAN message from a raw can_frame structure. + explicit CanMessageT(const struct can_frame& frame, const Duration& timestampOffset): m_canIdentifier(frame.can_id), m_timestampOffset(timestampOffset), m_rawFrame(frame) { } //!< Constructs a CAN message from a raw can_frame structure with a timestamp offset. + + /** + * @brief Constructs a CAN message from a CAN ID and a frame data string. + * + * @param canId The CAN ID of the message. + * @param frameData The data of the CAN frame as a string. + */ + explicit CanMessageT(const CanId& canId, const string& frameData): m_canIdentifier(canId) { + if (frameData.size() > CAN_MAX_DLEN) { + throw system_error(std::make_error_code(std::errc::message_size), "Payload too big!"); } - struct can_frame rawFrame; - rawFrame.can_id = canId; - memcpy(rawFrame.data, frameData.data(), frameData.size()); - rawFrame.can_dlc = frameData.size(); + m_rawFrame.can_id = canId; + std::copy(frameData.begin(), frameData.end(), m_rawFrame.data); + m_rawFrame.can_dlc = frameData.size(); + } + + explicit CanMessageT(const CanId& canId, const string& frameData, const Duration& timestampOffset): CanMessageT(canId, frameData) { + m_timestampOffset = timestampOffset; + } //!< Constructs a CAN message from a CAN ID and a frame data string with a timestamp offset. + + #if __cpp_lib_span >= 201907L + /** + * @brief Constructs a CAN message from a CAN ID and a span of bytes. + * + * @param canId The CAN ID of the message. + * @param frameData The data of the CAN frame as a span of bytes. + */ + explicit CanMessageT(const CanId& canId, const std::span& frameData): m_canIdentifier(canId) { + if (frameData.size() > CAN_MAX_DLEN) { + throw system_error(std::make_error_code(std::errc::message_size), "Payload too big!"); + } - _rawFrame = rawFrame; + m_rawFrame.can_id = canId; + std::copy(frameData.begin(), frameData.end(), m_rawFrame.data); + m_rawFrame.can_dlc = frameData.size(); } - virtual ~CanMessage() {} + explicit CanMessageT(const CanId& canId, const std::span& frameData, const Duration& timestampOffset): CanMessageT(canId, frameData) { + m_timestampOffset = timestampOffset; + } //!< Constructs a CAN message from a CAN ID and a span of bytes with a timestamp offset. + #endif // __cpp_lib_span >= 201907L + + CanMessageT(const CanMessageT& other) = default; //!< Copy constructor. + CanMessageT(CanMessageT&& other) noexcept = default; //!< Move constructor. + CanMessageT& operator=(const CanMessageT& other) = default; //!< Copy assignment operator. + CanMessageT& operator=(CanMessageT&& other) noexcept = default; //!< Move assignment operator. + + virtual ~ CanMessageT() = default; + + public: // +++ Public API +++ + const CanId& getCanId() const noexcept { return m_canIdentifier; } //!< Returns the CAN ID of this message. - public: // +++ Getters +++ - const CanId getCanId() const { return this->_canIdentifier; } - const string getFrameData() const { return this->_frameData; } - const can_frame getRawFrame() const { return this->_rawFrame; } + const string getFrameData() const noexcept { + string data{}; + data.reserve(m_rawFrame.can_dlc); + std::copy(std::begin(m_rawFrame.data), std::begin(m_rawFrame.data) + m_rawFrame.can_dlc, std::back_inserter(data)); + + return data; + } //!< Returns the frame data as a string. + + friend std::ostream& operator<<(std::ostream& os, const CanMessageT& msg) { + os << "CanMessage(canId: " << msg.getCanId() << ", data: "; + for (size_t i = 0; i < msg.m_rawFrame.can_dlc; ++i) { + os << std::hex << static_cast(msg.m_rawFrame.data[i]) << " "; + } + os << ", timestampOffset: " << msg.m_timestampOffset.count() << "ms)"; + return os; + } //!< Outputs the CanMessage to a stream in a human-readable format. + + const can_frame& getRawFrame() const noexcept { return m_rawFrame; } //!< Returns the raw can_frame structure of this message. + + const Duration& getTimestampOffset() const noexcept { return m_timestampOffset; } //!< Returns the timestamp offset of this message. + + NO_DISCARD constexpr bool isErrorFrame() const noexcept { return m_canIdentifier.hasErrorFrameFlag(); } //!< Checks if the CAN message is an error frame. + + NO_DISCARD constexpr bool isRemoteTransmissionRequest() const noexcept { return m_canIdentifier.hasRtrFrameFlag(); } //!< Checks if the CAN message is a remote transmission request. + + NO_DISCARD constexpr bool isStandardFrameId() const noexcept { return m_canIdentifier.isStandardFrameId(); } //!< Checks if the CAN message has a standard frame ID. + + NO_DISCARD constexpr bool isExtendedFrameId() const noexcept { return m_canIdentifier.isExtendedFrameId(); } //!< Checks if the CAN message has an extended frame ID. + + public: // +++ Error Frame Handling +++ + constexpr bool hasBusError() const { return m_canIdentifier.hasBusError(); } + constexpr bool hasBusOffError() const { return m_canIdentifier.hasBusOffError(); } + constexpr bool hasControllerProblem() const { return m_canIdentifier.hasControllerProblem(); } + constexpr bool hasControllerRestarted() const { return m_canIdentifier.hasControllerRestarted(); } + constexpr bool hasErrorCounter() const { return m_canIdentifier.hasErrorCounter(); } + constexpr bool hasLostArbitration() const { return m_canIdentifier.hasLostArbitration(); } + constexpr bool hasProtocolViolation() const { return m_canIdentifier.hasProtocolViolation(); } + constexpr bool hasTransceiverStatus() const { return m_canIdentifier.hasTransceiverStatus(); } + constexpr bool missingAckOnTransmit() const { return m_canIdentifier.missingAckOnTransmit(); } + constexpr bool isTxTimeout() const { return m_canIdentifier.isTxTimeout(); } + + public: // +++ Errors +++ + can_errors::ControllerError getControllerError() const { return can_errors::ControllerError::fromErrorCode(static_cast(m_rawFrame.data[1])); } + can_errors::ProtocolError getProtocolError() const { return can_errors::ProtocolError::fromErrorCode(static_cast(m_rawFrame.data[2]), static_cast(m_rawFrame.data[3])); } + can_errors::TransceiverError getTransceiverError() const { return can_errors::TransceiverError::fromErrorCode(static_cast(m_rawFrame.data[4])); } + size_t getTxErrorCounter() const { return m_rawFrame.data[6]; } + size_t getRxErrorCounter() const { return m_rawFrame.data[7]; } + uint8_t arbitrationLostInBit() const { return m_rawFrame.data[0]; } + + public: // +++ Equality Checks +++ + bool operator==(const CanMessageT& other) const noexcept { + return m_canIdentifier == other.m_canIdentifier && + m_rawFrame.can_dlc == other.m_rawFrame.can_dlc && + std::equal( + std::begin(m_rawFrame.data), std::begin(m_rawFrame.data) + m_rawFrame.can_dlc, + std::begin(other.m_rawFrame.data), std::begin(other.m_rawFrame.data) + other.m_rawFrame.can_dlc + ); + } //!< Compares this CAN message to another for equality. + + bool operator!=(const CanMessageT& other) const noexcept { + return !(*this == other); + } //!< Compares this CAN message to another for inequality. private: - CanId _canIdentifier; + CanId m_canIdentifier{}; - string _frameData; + Duration m_timestampOffset{}; - struct can_frame _rawFrame; + struct can_frame m_rawFrame{}; }; + using CanMessage = CanMessageT; //!< Default CAN message type with milliseconds as the timestamp offset. + } #endif // LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP diff --git a/include/EnumCheck.hpp b/include/EnumCheck.hpp new file mode 100644 index 0000000..a83db02 --- /dev/null +++ b/include/EnumCheck.hpp @@ -0,0 +1,41 @@ +/** + * @file EnumCheck.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the implementation of a constexpr enumeration value checker. + * @version 0.1 + * @date 2025-03-25 + * + * The entirety of this code is copied from https://stackoverflow.com/a/33091821/2921426 + * + * @copyright Copyright (c) 2025 Simon Cahill. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_ENUMCHECK_HPP +#define LIBSOCKCANPP_INCLUDE_ENUMCHECK_HPP + +namespace sockcanpp { + + template + class EnumCheck; + + template + class EnumCheck { + public: + template + static bool constexpr is_value(IntType) { return false; } + }; + + template + class EnumCheck : private EnumCheck { + using super = EnumCheck; + + public: + template + static bool constexpr is_value(IntType v) { + return v == static_cast(V) || super::is_value(v); + } + }; + +} + +#endif // LIBSOCKCANPP_INCLUDE_ENUMCHECK_HPP \ No newline at end of file diff --git a/include/TlOptional.hpp b/include/TlOptional.hpp new file mode 100644 index 0000000..07b3f93 --- /dev/null +++ b/include/TlOptional.hpp @@ -0,0 +1,2062 @@ + +/// +// optional - An implementation of std::optional with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_OPTIONAL_HPP +#define TL_OPTIONAL_HPP + +#define TL_OPTIONAL_VERSION_MAJOR 1 +#define TL_OPTIONAL_VERSION_MINOR 1 +#define TL_OPTIONAL_VERSION_PATCH 0 + +#include +#include +#include +#include +#include + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_OPTIONAL_MSVC2015 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +#define TL_OPTIONAL_NO_CONSTRR + +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && \ + !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { + namespace detail { + template + struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; +#ifdef _GLIBCXX_VECTOR + template + struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible{}; +#endif + } +} +#endif + +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#else +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#endif + +#if __cplusplus > 201103L +#define TL_OPTIONAL_CXX14 +#endif + +// constexpr implies const in C++11, not C++14 +#if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ + defined(TL_OPTIONAL_GCC49)) +#define TL_OPTIONAL_11_CONSTEXPR +#else +#define TL_OPTIONAL_11_CONSTEXPR constexpr +#endif + +namespace tl { +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +/// Used to represent an optional with no data; essentially a bool +class monostate {}; + +/// A tag type to tell optional to construct its value in-place +struct in_place_t { + explicit in_place_t() = default; +}; +/// A tag to tell optional to construct its value in-place +static constexpr in_place_t in_place{}; +#endif + +template class optional; + +namespace detail { +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template struct is_pointer_to_non_const_member_func : std::false_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; + +template struct is_const_or_const_ref : std::false_type{}; +template struct is_const_or_const_ref : std::true_type{}; +template struct is_const_or_const_ref : std::true_type{}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template ::value + && is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, + int = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template tag swap(T &, T &); +template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + &&detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { +}; +#endif +#endif + +// std::void_t from C++17 +template struct voider { using type = void; }; +template using void_t = typename voider::type; + +// Trait for checking if a type is a tl::optional +template struct is_optional_impl : std::false_type {}; +template struct is_optional_impl> : std::true_type {}; +template using is_optional = is_optional_impl>; + +// Change void to tl::monostate +template +using fixup_void = conditional_t::value, monostate, U>; + +template > +using get_map_return = optional>>; + +// Check if invoking F for some Us returns void +template struct returns_void_impl; +template +struct returns_void_impl>, U...> + : std::is_void> {}; +template +using returns_void = returns_void_impl; + +template +using enable_if_ret_void = enable_if_t::value>; + +template +using disable_if_ret_void = enable_if_t::value>; + +template +using enable_forward_value = + detail::enable_if_t::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using enable_from_other = detail::enable_if_t< + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using enable_assign_forward = detail::enable_if_t< + !std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && std::is_assignable::value>; + +template +using enable_assign_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_assignable::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value>; + +// The storage base manages the actual storage, and correctly propagates +// trivial destruction from T. This case is for when T is not trivially +// destructible. +template ::value> +struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + ~optional_storage_base() { + if (m_has_value) { + m_value.~T(); + m_has_value = false; + } + } + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value; +}; + +// This case is for when T is trivially destructible. +template struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + // No destructor, so this class is trivially destructible + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value = false; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template struct optional_operations_base : optional_storage_base { + using optional_storage_base::optional_storage_base; + + void hard_reset() noexcept { + get().~T(); + this->m_has_value = false; + } + + template void construct(Args &&... args) { + new (std::addressof(this->m_value)) T(std::forward(args)...); + this->m_has_value = true; + } + + template void assign(Opt &&rhs) { + if (this->has_value()) { + if (rhs.has_value()) { + this->m_value = std::forward(rhs).get(); + } else { + this->m_value.~T(); + this->m_has_value = false; + } + } + + else if (rhs.has_value()) { + construct(std::forward(rhs).get()); + } + } + + bool has_value() const { return this->m_has_value; } + + TL_OPTIONAL_11_CONSTEXPR T &get() & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR const T &get() const & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR T &&get() && { return std::move(this->m_value); } +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_value); } +#endif +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T is trivially copy constructible +template +struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; +}; + +// This specialization is for when T is not trivially copy constructible +template +struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + + optional_copy_base() = default; + optional_copy_base(const optional_copy_base &rhs) + : optional_operations_base() { + if (rhs.has_value()) { + this->construct(rhs.get()); + } else { + this->m_has_value = false; + } + } + + optional_copy_base(optional_copy_base &&rhs) = default; + optional_copy_base &operator=(const optional_copy_base &rhs) = default; + optional_copy_base &operator=(optional_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_OPTIONAL_GCC49 +template ::value> +struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; +}; +#else +template struct optional_move_base; +#endif +template struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + + optional_move_base() = default; + optional_move_base(const optional_move_base &rhs) = default; + + optional_move_base(optional_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) { + if (rhs.has_value()) { + this->construct(std::move(rhs.get())); + } else { + this->m_has_value = false; + } + } + optional_move_base &operator=(const optional_move_base &rhs) = default; + optional_move_base &operator=(optional_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template +struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; +}; + +template +struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + + optional_copy_assign_base() = default; + optional_copy_assign_base(const optional_copy_assign_base &rhs) = default; + + optional_copy_assign_base(optional_copy_assign_base &&rhs) = default; + optional_copy_assign_base &operator=(const optional_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + optional_copy_assign_base & + operator=(optional_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_OPTIONAL_GCC49 +template ::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; +}; +#else +template struct optional_move_assign_base; +#endif + +template +struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + + optional_move_assign_base() = default; + optional_move_assign_base(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base(optional_move_assign_base &&rhs) = default; + + optional_move_assign_base & + operator=(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base & + operator=(optional_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// optional_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value, + bool EnableMove = std::is_move_constructible::value> +struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +// optional_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible + assignable +template ::value && + std::is_copy_assignable::value), + bool EnableMove = (std::is_move_constructible::value && + std::is_move_assignable::value)> +struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = default; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = default; +}; + +template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = default; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = delete; +}; + +template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = delete; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = default; +}; + +template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = delete; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = delete; +}; + +} // namespace detail + +/// A tag type to represent an empty optional +struct nullopt_t { + struct do_not_use {}; + constexpr explicit nullopt_t(do_not_use, do_not_use) noexcept {} +}; +/// Represents an empty optional +static constexpr nullopt_t nullopt{nullopt_t::do_not_use{}, + nullopt_t::do_not_use{}}; + +class bad_optional_access : public std::exception { +public: + bad_optional_access() = default; + const char *what() const noexcept { return "Optional has no value"; } +}; + +/// An optional object is an object that contains the storage for another +/// object and manages the lifetime of this contained object, if any. The +/// contained object may be initialized after the optional object has been +/// initialized, and may be destroyed before the optional object has been +/// destroyed. The initialization state of the contained object is tracked by +/// the optional object. +template +class optional : private detail::optional_move_assign_base, + private detail::optional_delete_ctor_base, + private detail::optional_delete_assign_base { + using base = detail::optional_move_assign_base; + + static_assert(!std::is_same::value, + "instantiation of optional with in_place_t is ill-formed"); + static_assert(!std::is_same, nullopt_t>::value, + "instantiation of optional with nullopt_t is ill-formed"); + +public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u`. + template U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept = default; + + constexpr optional(nullopt_t) noexcept {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value in-place using the given arguments. + template + constexpr explicit optional( + detail::enable_if_t::value, in_place_t>, + Args &&... args) + : base(in_place, std::forward(args)...) {} + + template + TL_OPTIONAL_11_CONSTEXPR explicit optional( + detail::enable_if_t &, + Args &&...>::value, + in_place_t>, + std::initializer_list il, Args &&... args) { + this->construct(il, std::forward(args)...); + } + + /// Constructs the stored value with `u`. + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr optional(U &&u) : base(in_place, std::forward(u)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr explicit optional(U &&u) : base(in_place, std::forward(u)) {} + + /// Converting copy constructor. + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + template * = nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + /// Converting move constructor. + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + /// Destroys the stored value if there is one. + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + + return *this; + } + + /// Copy assignment. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Move assignment. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(optional &&rhs) = default; + + /// Assigns the stored value from `u`, destroying the old value if there was + /// one. + template * = nullptr> + optional &operator=(U &&u) { + if (has_value()) { + this->m_value = std::forward(u); + } else { + this->construct(std::forward(u)); + } + + return *this; + } + + /// Converting copy assignment operator. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(const optional &rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = *rhs; + } else { + this->hard_reset(); + } + } + + else if (rhs.has_value()) { + this->construct(*rhs); + } + + return *this; + } + + // TODO check exception guarantee + /// Converting move assignment operator. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(optional &&rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = std::move(*rhs); + } else { + this->hard_reset(); + } + } + + else if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + + return *this; + } + + /// Constructs the value in-place, destroying the current one if there is + /// one. + template T &emplace(Args &&... args) { + static_assert(std::is_constructible::value, + "T must be constructible with Args"); + + *this = nullopt; + this->construct(std::forward(args)...); + return value(); + } + + template + detail::enable_if_t< + std::is_constructible &, Args &&...>::value, + T &> + emplace(std::initializer_list il, Args &&... args) { + *this = nullopt; + this->construct(il, std::forward(args)...); + return value(); + } + + /// Swaps this optional with the other. + /// + /// If neither optionals have a value, nothing happens. + /// If both have a value, the values are swapped. + /// If one has a value, it is moved to the other and the movee is left + /// valueless. + void + swap(optional &rhs) noexcept(std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + using std::swap; + if (has_value()) { + if (rhs.has_value()) { + swap(**this, *rhs); + } else { + new (std::addressof(rhs.m_value)) T(std::move(this->m_value)); + this->m_value.T::~T(); + } + } else if (rhs.has_value()) { + new (std::addressof(this->m_value)) T(std::move(rhs.m_value)); + rhs.m_value.T::~T(); + } + swap(this->m_has_value, rhs.m_has_value); + } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const { + return std::addressof(this->m_value); + } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() { + return std::addressof(this->m_value); + } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() & { return this->m_value; } + + constexpr const T &operator*() const & { return this->m_value; } + + TL_OPTIONAL_11_CONSTEXPR T &&operator*() && { + return std::move(this->m_value); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&operator*() const && { return std::move(this->m_value); } +#endif + + /// Returns whether or not the optional has a value + constexpr bool has_value() const noexcept { return this->m_has_value; } + + constexpr explicit operator bool() const noexcept { + return this->m_has_value; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() & { + if (has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const & { + if (has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR T &&value() && { + if (has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + TL_OPTIONAL_11_CONSTEXPR const T &&value() const && { + if (has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } +#endif + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U &&u) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? std::move(**this) : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + } +}; // namespace tl + +/// Compares two optional objects +template +inline constexpr bool operator==(const optional &lhs, + const optional &rhs) { + return lhs.has_value() == rhs.has_value() && + (!lhs.has_value() || *lhs == *rhs); +} +template +inline constexpr bool operator!=(const optional &lhs, + const optional &rhs) { + return lhs.has_value() != rhs.has_value() || + (lhs.has_value() && *lhs != *rhs); +} +template +inline constexpr bool operator<(const optional &lhs, + const optional &rhs) { + return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); +} +template +inline constexpr bool operator>(const optional &lhs, + const optional &rhs) { + return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); +} +template +inline constexpr bool operator<=(const optional &lhs, + const optional &rhs) { + return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); +} +template +inline constexpr bool operator>=(const optional &lhs, + const optional &rhs) { + return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); +} + +/// Compares an optional to a `nullopt` +template +inline constexpr bool operator==(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); +} +template +inline constexpr bool operator==(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); +} +template +inline constexpr bool operator!=(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); +} +template +inline constexpr bool operator!=(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); +} +template +inline constexpr bool operator<(const optional &, nullopt_t) noexcept { + return false; +} +template +inline constexpr bool operator<(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); +} +template +inline constexpr bool operator<=(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); +} +template +inline constexpr bool operator<=(nullopt_t, const optional &) noexcept { + return true; +} +template +inline constexpr bool operator>(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); +} +template +inline constexpr bool operator>(nullopt_t, const optional &) noexcept { + return false; +} +template +inline constexpr bool operator>=(const optional &, nullopt_t) noexcept { + return true; +} +template +inline constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); +} + +/// Compares the optional with a value. +template +inline constexpr bool operator==(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs == rhs : false; +} +template +inline constexpr bool operator==(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs == *rhs : false; +} +template +inline constexpr bool operator!=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs != rhs : true; +} +template +inline constexpr bool operator!=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs != *rhs : true; +} +template +inline constexpr bool operator<(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs < rhs : true; +} +template +inline constexpr bool operator<(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs < *rhs : false; +} +template +inline constexpr bool operator<=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs <= rhs : true; +} +template +inline constexpr bool operator<=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs <= *rhs : false; +} +template +inline constexpr bool operator>(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs > rhs : false; +} +template +inline constexpr bool operator>(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs > *rhs : true; +} +template +inline constexpr bool operator>=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs >= rhs : false; +} +template +inline constexpr bool operator>=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs >= *rhs : true; +} + +template ::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> +void swap(optional &lhs, + optional &rhs) noexcept(noexcept(lhs.swap(rhs))) { + return lhs.swap(rhs); +} + +namespace detail { +struct i_am_secret {}; +} // namespace detail + +template ::value, + detail::decay_t, T>> +inline constexpr optional make_optional(U &&v) { + return optional(std::forward(v)); +} + +template +inline constexpr optional make_optional(Args &&... args) { + return optional(in_place, std::forward(args)...); +} +template +inline constexpr optional make_optional(std::initializer_list il, + Args &&... args) { + return optional(in_place, il, std::forward(args)...); +} + +#if __cplusplus >= 201703L +template optional(T)->optional; +#endif + +/// \exclude +namespace detail { +#ifdef TL_OPTIONAL_CXX14 +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto optional_map_impl(Opt &&opt, F &&f) { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto optional_map_impl(Opt &&opt, F &&f) { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return make_optional(monostate{}); + } + + return optional(nullopt); +} +#else +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto optional_map_impl(Opt &&opt, F &&f) -> optional { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto optional_map_impl(Opt &&opt, F &&f) -> optional { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return nullopt; +} +#endif +} // namespace detail + +/// Specialization for when `T` is a reference. `optional` acts similarly +/// to a `T*`, but provides more operations and shows intent more clearly. +template class optional { +public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template auto transform(F &&f) &&; + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u` + template U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T &; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept : m_value(nullptr) {} + + constexpr optional(nullopt_t) noexcept : m_value(nullptr) {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) noexcept = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value with `u`. + template >::value> + * = nullptr> + constexpr optional(U &&u) noexcept : m_value(std::addressof(u)) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + } + + template + constexpr explicit optional(const optional &rhs) noexcept : optional(*rhs) {} + + /// No-op + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + m_value = nullptr; + return *this; + } + + /// Copy assignment. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional &operator=(U &&u) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + m_value = std::addressof(u); + return *this; + } + + /// Converting copy assignment operator. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + template optional &operator=(const optional &rhs) { + m_value = std::addressof(rhs.value()); + return *this; + } + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional &emplace(U &&u) noexcept { + return *this = std::forward(u); + } + + void swap(optional &rhs) noexcept { std::swap(m_value, rhs.m_value); } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const noexcept { return m_value; } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() noexcept { return m_value; } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() noexcept { return *m_value; } + + constexpr const T &operator*() const noexcept { return *m_value; } + + constexpr bool has_value() const noexcept { return m_value != nullptr; } + + constexpr explicit operator bool() const noexcept { + return m_value != nullptr; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() { + if (has_value()) + return *m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const { + if (has_value()) + return *m_value; + throw bad_optional_access(); + } + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U &&u) const & noexcept { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// \group value_or + template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && noexcept { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { m_value = nullptr; } + +private: + T *m_value; +}; // namespace tl + + + +} // namespace tl + +namespace std { +// TODO SFINAE +template struct hash> { + ::std::size_t operator()(const tl::optional &o) const { + if (!o.has_value()) + return 0; + + return std::hash>()(*o); + } +}; +} // namespace std + +#endif \ No newline at end of file diff --git a/include/can_errors/CanControllerError.hpp b/include/can_errors/CanControllerError.hpp new file mode 100644 index 0000000..eebf582 --- /dev/null +++ b/include/can_errors/CanControllerError.hpp @@ -0,0 +1,87 @@ +/** + * @file CanControllerError.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the definition of CAN controller error codes. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANCONTROLLERERROR_HPP +#define LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANCONTROLLERERROR_HPP + +#include +#include + +#include + +#if __cplusplus >= 201703L +namespace sockcanpp::can_errors { +#else +namespace sockcanpp { namespace can_errors { +#endif // __cplusplus >= 201703L + + using std::string; + + /** + * @brief Contains a typesafe representation of CAN controller error codes. + */ + enum class ControllerErrorCode: uint8_t { + UNSPECIFIED_ERROR = CAN_ERR_CRTL_UNSPEC, //!< Unspecified error. + RECEIVE_OVERFLOW = CAN_ERR_CRTL_RX_OVERFLOW, //!< Receive overflow error. + TRANSMIT_OVERFLOW = CAN_ERR_CRTL_TX_OVERFLOW, //!< Transmit overflow error. + RECEIVE_WARNING = CAN_ERR_CRTL_RX_WARNING, //!< Receive warning error. + TRANSMIT_WARNING = CAN_ERR_CRTL_TX_WARNING, //!< Transmit warning error. + RECEIVE_PASSIVE = CAN_ERR_CRTL_RX_PASSIVE, //!< Receive passive error. + TRANSMIT_PASSIVE = CAN_ERR_CRTL_TX_PASSIVE, //!< Transmit passive error. + RECOVERED_ACTIVE = CAN_ERR_CRTL_ACTIVE, //!< Recovered to active state. + }; + + struct ControllerError { + ControllerErrorCode errorCode; + string errorMessage; + + ControllerError(ControllerErrorCode code, const string& message): errorCode(code), errorMessage(message) { } + + static ControllerError fromErrorCode(ControllerErrorCode code) { + switch (code) { + case ControllerErrorCode::UNSPECIFIED_ERROR: return ControllerError(code, "Unspecified error"); + case ControllerErrorCode::RECEIVE_OVERFLOW: return ControllerError(code, "Receive overflow error"); + case ControllerErrorCode::TRANSMIT_OVERFLOW: return ControllerError(code, "Transmit overflow error"); + case ControllerErrorCode::RECEIVE_WARNING: return ControllerError(code, "Receive warning error"); + case ControllerErrorCode::TRANSMIT_WARNING: return ControllerError(code, "Transmit warning error"); + case ControllerErrorCode::RECEIVE_PASSIVE: return ControllerError(code, "Receive passive error"); + case ControllerErrorCode::TRANSMIT_PASSIVE: return ControllerError(code, "Transmit passive error"); + case ControllerErrorCode::RECOVERED_ACTIVE: return ControllerError(code, "Recovered to active state"); + default: return ControllerError(code, "Unknown error"); + } + } + }; + +#if __cplusplus >= 201703L +} // namespace sockcanpp::can_errors +#else +} /* namespace can_errors */ } /* namespace sockcanpp */ +#endif // __cplusplus >= 201703L + +namespace std { + + inline string to_string(sockcanpp::can_errors::ControllerErrorCode err) { + using sockcanpp::can_errors::ControllerErrorCode; + switch (err) { + case ControllerErrorCode::UNSPECIFIED_ERROR: return "Unspecified error"; + case ControllerErrorCode::RECEIVE_OVERFLOW: return "Receive overflow error"; + case ControllerErrorCode::TRANSMIT_OVERFLOW: return "Transmit overflow error"; + case ControllerErrorCode::RECEIVE_WARNING: return "Receive warning error"; + case ControllerErrorCode::TRANSMIT_WARNING: return "Transmit warning error"; + case ControllerErrorCode::RECEIVE_PASSIVE: return "Receive passive error"; + case ControllerErrorCode::TRANSMIT_PASSIVE: return "Transmit passive error"; + case ControllerErrorCode::RECOVERED_ACTIVE: return "Recovered to active state"; + default: return "Unknown controller error"; + } + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANCONTROLLERERROR_HPP \ No newline at end of file diff --git a/include/can_errors/CanProtocolError.hpp b/include/can_errors/CanProtocolError.hpp new file mode 100644 index 0000000..e3cd2c3 --- /dev/null +++ b/include/can_errors/CanProtocolError.hpp @@ -0,0 +1,160 @@ +/** + * @file CanProtocolError.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the definition of CAN protocol error codes. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANPROTOCOLERROR_HPP +#define LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANPROTOCOLERROR_HPP + +#include +#include + +#include + +#if __cplusplus >= 201703L +namespace sockcanpp::can_errors { +#else +namespace sockcanpp { namespace can_errors { +#endif // __cplusplus >= 201703L + + using std::string; + + /** + * @brief Contains a typesafe representation of CAN protocol error codes. + */ + enum class ProtocolErrorCode: uint8_t { + UNSPECIFIED_ERROR = CAN_ERR_PROT_UNSPEC, //!< Unspecified error occurred. + SINGLE_BIT_ERROR = CAN_ERR_PROT_BIT, //!< A single bit error occurred. + FRAME_FORMAT_ERROR = CAN_ERR_PROT_FORM, //!< A frame format error occurred. + BIT_STUFFING_ERROR = CAN_ERR_PROT_STUFF, //!< A bit stuffing error occurred. + DOMINANT_BIT_FAIL = CAN_ERR_PROT_BIT0, //!< A dominant bit failure occurred. + RECESSIVE_BIT_FAIL = CAN_ERR_PROT_BIT1, //!< A recessive bit failure occurred. + OVERLOAD_ERROR = CAN_ERR_PROT_OVERLOAD,//!< An overload error occurred. + ACTIVE_ERROR = CAN_ERR_PROT_ACTIVE, //!< An active error occurred. + TX_ERROR = CAN_ERR_PROT_TX //!< A transmission error occurred. + }; + + /** + * @brief Contains a typesafe representation of CAN protocol error locations. + */ + enum class ProtocolErrorLocation: uint8_t { + UNSPECIFIED_LOCATION = CAN_ERR_PROT_LOC_UNSPEC, //!< Unspecified location. + START_OF_FRAME = CAN_ERR_PROT_LOC_SOF, //!< Start of frame. + IDBIT_28_21 = CAN_ERR_PROT_LOC_ID28_21, //!< ID bits 28 - 21 (SFF: 10 - 3) + IDBIT_20_18 = CAN_ERR_PROT_LOC_ID20_18, //!< ID bits 20 - 18 (SFF: 2 - 0) + SUBSTITUTE_RTR = CAN_ERR_PROT_LOC_SRTR, //!< Substitute RTR bit. + IDENTIFIER_EXTENSION = CAN_ERR_PROT_LOC_IDE, //!< Identifier extension bit. + IDBIT_17_13 = CAN_ERR_PROT_LOC_ID17_13, //!< ID bits 17 - 13 + IDBIT_12_05 = CAN_ERR_PROT_LOC_ID12_05, //!< ID bits 12 - 05 + IDBIT_04_00 = CAN_ERR_PROT_LOC_ID04_00, //!< ID bits 04 - 00 + REMOTE_TRANSMIT_REQ = CAN_ERR_PROT_LOC_RTR, //!< Remote transmit request bit. + RESERVED_BIT_1 = CAN_ERR_PROT_LOC_RES1, //!< Reserved bit 1. + RESERVED_BIT_0 = CAN_ERR_PROT_LOC_RES0, //!< Reserved bit 0. + DATA_LENGTH_CODE = CAN_ERR_PROT_LOC_DLC, //!< Data length code. + DATA_SECTION = CAN_ERR_PROT_LOC_DATA, //!< Data section. + CRC_SECTION = CAN_ERR_PROT_LOC_CRC_SEQ, //!< CRC section. + CRC_DELIMITER = CAN_ERR_PROT_LOC_CRC_DEL, //!< CRC delimiter. + ACK_SLOT = CAN_ERR_PROT_LOC_ACK, //!< ACK slot. + ACK_DELIMITER = CAN_ERR_PROT_LOC_ACK_DEL, //!< ACK delimiter. + END_OF_FRAME = CAN_ERR_PROT_LOC_EOF, //!< End of frame. + INTERMISSION = CAN_ERR_PROT_LOC_INTERM, //!< Intermission section. + }; + + struct ProtocolError { + ProtocolErrorCode errorCode; + ProtocolErrorLocation errorLocation; + string errorMessage; + + ProtocolError(ProtocolErrorCode code, ProtocolErrorLocation location, const string& message): errorCode(code), errorLocation(location), errorMessage(message) { } + + static ProtocolError fromErrorCode(ProtocolErrorCode code, ProtocolErrorLocation location) { + switch (code) { + case ProtocolErrorCode::UNSPECIFIED_ERROR: return ProtocolError(code, location, "Unspecified error occurred"); + case ProtocolErrorCode::SINGLE_BIT_ERROR: return ProtocolError(code, location, "Single bit error occurred"); + case ProtocolErrorCode::FRAME_FORMAT_ERROR: return ProtocolError(code, location, "Frame format error occurred"); + case ProtocolErrorCode::BIT_STUFFING_ERROR: return ProtocolError(code, location, "Bit stuffing error occurred"); + case ProtocolErrorCode::DOMINANT_BIT_FAIL: return ProtocolError(code, location, "Dominant bit failure occurred"); + case ProtocolErrorCode::RECESSIVE_BIT_FAIL: return ProtocolError(code, location, "Recessive bit failure occurred"); + case ProtocolErrorCode::OVERLOAD_ERROR: return ProtocolError(code, location, "Overload error occurred"); + case ProtocolErrorCode::ACTIVE_ERROR: return ProtocolError(code, location, "Active error occurred"); + case ProtocolErrorCode::TX_ERROR: return ProtocolError(code, location, "Transmission error occurred"); + default: return ProtocolError(code, location, "Unknown error occurred"); + } + } + }; + +#if __cplusplus >= 201703L +} // namespace sockcanpp::can_errors +#else +} /* namespace can_errors */ } /* namespace sockcanpp */ +#endif // __cplusplus >= 201703L + +namespace std { + + inline string to_string(sockcanpp::can_errors::ProtocolErrorLocation loc) { + using sockcanpp::can_errors::ProtocolErrorLocation; + switch (loc) { + case ProtocolErrorLocation::UNSPECIFIED_LOCATION: return "Unspecified location."; + case ProtocolErrorLocation::START_OF_FRAME: return "Start of frame."; + case ProtocolErrorLocation::IDBIT_28_21: return "ID bits 28 - 21 (SFF: 10 - 3)"; + case ProtocolErrorLocation::IDBIT_20_18: return "ID bits 20 - 18 (SFF: 2 - 0)"; + case ProtocolErrorLocation::SUBSTITUTE_RTR: return "Substitute RTR bit."; + case ProtocolErrorLocation::IDENTIFIER_EXTENSION: return "Identifier extension bit."; + case ProtocolErrorLocation::IDBIT_17_13: return "ID bits 17 - 13"; + case ProtocolErrorLocation::IDBIT_12_05: return "ID bits 12 - 05"; + case ProtocolErrorLocation::IDBIT_04_00: return "ID bits 04 - 00"; + case ProtocolErrorLocation::REMOTE_TRANSMIT_REQ: return "Remote transmit request bit."; + case ProtocolErrorLocation::RESERVED_BIT_1: return "Reserved bit 1."; + case ProtocolErrorLocation::RESERVED_BIT_0: return "Reserved bit 0."; + case ProtocolErrorLocation::DATA_LENGTH_CODE: return "Data length code."; + case ProtocolErrorLocation::DATA_SECTION: return "Data section."; + case ProtocolErrorLocation::CRC_SECTION: return "CRC section."; + case ProtocolErrorLocation::CRC_DELIMITER: return "CRC delimiter."; + case ProtocolErrorLocation::ACK_SLOT: return "ACK slot."; + case ProtocolErrorLocation::ACK_DELIMITER: return "ACK delimiter."; + case ProtocolErrorLocation::END_OF_FRAME: return "End of frame."; + case ProtocolErrorLocation::INTERMISSION: return "Intermission section."; + default: return "Unknown location."; + } + } + + inline string to_string(sockcanpp::can_errors::ProtocolErrorCode err) { + using sockcanpp::can_errors::ProtocolErrorCode; + switch (err) { + case ProtocolErrorCode::UNSPECIFIED_ERROR: return "Unspecified error occurred"; + case ProtocolErrorCode::SINGLE_BIT_ERROR: return "Single bit error occurred"; + case ProtocolErrorCode::FRAME_FORMAT_ERROR: return "Frame format error occurred"; + case ProtocolErrorCode::BIT_STUFFING_ERROR: return "Bit stuffing error occurred"; + case ProtocolErrorCode::DOMINANT_BIT_FAIL: return "Dominant bit failure occurred"; + case ProtocolErrorCode::RECESSIVE_BIT_FAIL: return "Recessive bit failure occurred"; + case ProtocolErrorCode::OVERLOAD_ERROR: return "Overload error occurred"; + case ProtocolErrorCode::ACTIVE_ERROR: return "Active error occurred"; + case ProtocolErrorCode::TX_ERROR: return "Transmission error occurred"; + default: return "Unknown error occurred"; + } + } + + inline string to_string(sockcanpp::can_errors::ProtocolErrorCode err, sockcanpp::can_errors::ProtocolErrorLocation loc) { + using sockcanpp::can_errors::ProtocolErrorCode; + switch (err) { + case ProtocolErrorCode::UNSPECIFIED_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::SINGLE_BIT_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::FRAME_FORMAT_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::BIT_STUFFING_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::DOMINANT_BIT_FAIL: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::RECESSIVE_BIT_FAIL: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::OVERLOAD_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::ACTIVE_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::TX_ERROR: return to_string(err) + " at " + to_string(loc); + default: return to_string(err) + " at " + to_string(loc); + } + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANPROTOCOLERROR_HPP \ No newline at end of file diff --git a/include/can_errors/CanTransceiverError.hpp b/include/can_errors/CanTransceiverError.hpp new file mode 100644 index 0000000..ce56151 --- /dev/null +++ b/include/can_errors/CanTransceiverError.hpp @@ -0,0 +1,95 @@ +/** + * @file CanTransceiverError.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the definition of CAN transceiver error codes. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANTRANSCEIVERERROR_HPP +#define LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANTRANSCEIVERERROR_HPP + +#include +#include + +#include + +#if __cplusplus >= 201703L +namespace sockcanpp::can_errors { +#else +namespace sockcanpp { namespace can_errors { +#endif // __cplusplus >= 201703L + + using std::string; + + /** + * @brief Contains a typesafe representation of CAN transceiver error codes. + */ + enum class TransceiverErrorCode: uint8_t { + // CANH CANL + UNSPECIFIED_ERROR = CAN_ERR_TRX_UNSPEC, //!< Unspecified error. 0000 0000 + CAN_HIGH_NO_WIRE = CAN_ERR_TRX_CANH_NO_WIRE, //!< CANH no wire error. 0000 0100 + CAN_HIGH_SHORT_TO_BAT = CAN_ERR_TRX_CANH_SHORT_TO_BAT, //!< CANH short to battery error. 0000 0101 + CAN_HIGH_SHORT_TO_VCC = CAN_ERR_TRX_CANH_SHORT_TO_VCC, //!< CANH short to VCC error. 0000 0110 + CAN_HIGH_SHORT_TO_GND = CAN_ERR_TRX_CANH_SHORT_TO_GND, //!< CANH short to ground error. 0000 0111 + + CAN_LOW_NO_WIRE = CAN_ERR_TRX_CANL_NO_WIRE, //!< CANL no wire error. 0100 0000 + CAN_LOW_SHORT_TO_BAT = CAN_ERR_TRX_CANL_SHORT_TO_BAT, //!< CANL short to battery error. 0101 0000 + CAN_LOW_SHORT_TO_VCC = CAN_ERR_TRX_CANL_SHORT_TO_VCC, //!< CANL short to VCC error. 0110 0000 + CAN_LOW_SHORT_TO_GND = CAN_ERR_TRX_CANL_SHORT_TO_GND, //!< CANL short to ground error. 0111 0000 + CAN_LOW_SHORT_TO_HIGH = CAN_ERR_TRX_CANL_SHORT_TO_CANH, //!< CANL short to CANH error. 1000 0000 + }; + + struct TransceiverError { + TransceiverErrorCode errorCode; + string errorMessage; + + TransceiverError(TransceiverErrorCode code, const string& message): errorCode(code), errorMessage(message) { } + + static TransceiverError fromErrorCode(TransceiverErrorCode code) { + switch (code) { + case TransceiverErrorCode::UNSPECIFIED_ERROR: return TransceiverError(code, "Unspecified error."); + case TransceiverErrorCode::CAN_HIGH_NO_WIRE: return TransceiverError(code, "CANH no wire error."); + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_BAT: return TransceiverError(code, "CANH short to battery error."); + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_VCC: return TransceiverError(code, "CANH short to VCC error."); + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_GND: return TransceiverError(code, "CANH short to ground error."); + case TransceiverErrorCode::CAN_LOW_NO_WIRE: return TransceiverError(code, "CANL no wire error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_BAT: return TransceiverError(code, "CANL short to battery error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_VCC: return TransceiverError(code, "CANL short to VCC error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_GND: return TransceiverError(code, "CANL short to ground error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_HIGH: return TransceiverError(code, "CANL short to CANH error."); + default: return TransceiverError(code, "Unknown error."); + } + } + }; + +#if __cplusplus >= 201703L +} // namespace sockcanpp::can_errors +#else +} /* namespace can_errors */ } /* namespace sockcanpp */ +#endif // __cplusplus >= 201703L + +namespace std { + + inline string to_string(sockcanpp::can_errors::TransceiverErrorCode err) { + using sockcanpp::can_errors::TransceiverErrorCode; + switch (err) { + case TransceiverErrorCode::UNSPECIFIED_ERROR: return "Unspecified error."; + case TransceiverErrorCode::CAN_HIGH_NO_WIRE: return "CANH no wire error."; + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_BAT: return "CANH short to battery error."; + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_VCC: return "CANH short to VCC error."; + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_GND: return "CANH short to ground error."; + case TransceiverErrorCode::CAN_LOW_NO_WIRE: return "CANL no wire error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_BAT: return "CANL short to battery error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_VCC: return "CANL short to VCC error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_GND: return "CANL short to ground error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_HIGH: return "CANL short to CANH error."; + default: return "Unknown error."; + } + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANTRANSCEIVERERROR_HPP \ No newline at end of file diff --git a/include/exceptions/CanCloseException.hpp b/include/exceptions/CanCloseException.hpp index 854d4cb..44fb7ec 100644 --- a/include/exceptions/CanCloseException.hpp +++ b/include/exceptions/CanCloseException.hpp @@ -1,6 +1,6 @@ /** * @file CanCloseException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of an exception that may be thrown when an error occurs while closing a CAN socket. * @version 0.1 * @date 2020-07-02 @@ -42,7 +42,7 @@ namespace sockcanpp { namespace exceptions { ~CanCloseException() {} public: // +++ Overrides +++ - const char* what() { return _message.c_str(); } + const char* what() const noexcept { return _message.c_str(); } private: string _message; @@ -50,4 +50,4 @@ namespace sockcanpp { namespace exceptions { } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP diff --git a/include/exceptions/CanException.hpp b/include/exceptions/CanException.hpp index 3c71f5b..b1ff8bf 100644 --- a/include/exceptions/CanException.hpp +++ b/include/exceptions/CanException.hpp @@ -1,6 +1,6 @@ /** * @file CanException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a general-purpose exception which may be thrown when an error occurs when performing IO on a CAN socket. * @version 0.1 * @date 2020-07-02 @@ -38,21 +38,21 @@ namespace sockcanpp { namespace exceptions { */ class CanException: public exception { public: // +++ Constructor / Destructor +++ - CanException(string message, int32_t socket): _message(message), _socket(socket) {} + CanException(const string& message, int32_t socket): m_socket(socket), m_message(message) {} ~CanException() {} public: // +++ Overrides +++ - const char* what() { return _message.c_str(); } + const char* what() const noexcept { return m_message.c_str(); } public: // +++ Getter +++ - const int32_t getSocket() const { return _socket; } + int32_t getSocket() const { return m_socket; } private: - int32_t _socket; + int32_t m_socket; - string _message; + string m_message; }; } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP diff --git a/include/exceptions/CanInitException.hpp b/include/exceptions/CanInitException.hpp index 8f3707a..e37bf7a 100644 --- a/include/exceptions/CanInitException.hpp +++ b/include/exceptions/CanInitException.hpp @@ -1,6 +1,6 @@ /** * @file CanInitException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of an exception that is thrown when a CAN socket couldn't be inintialised. * @version 0.1 * @date 2020-07-02 @@ -42,7 +42,7 @@ namespace sockcanpp { namespace exceptions { virtual ~CanInitException() { } public: // +++ Override +++ - const char* what() { return _message.c_str(); } + const char* what() const noexcept { return _message.c_str(); } private: string _message; @@ -50,4 +50,4 @@ namespace sockcanpp { namespace exceptions { } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP diff --git a/include/exceptions/InvalidSocketException.hpp b/include/exceptions/InvalidSocketException.hpp index f7ab7dd..a1da36d 100644 --- a/include/exceptions/InvalidSocketException.hpp +++ b/include/exceptions/InvalidSocketException.hpp @@ -1,6 +1,6 @@ /** * @file InvalidSocketException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of an exception that may be thrown when an invalid CAN socket is detected. * @version 0.1 * @date 2020-07-02 @@ -38,21 +38,21 @@ namespace sockcanpp { namespace exceptions { */ class InvalidSocketException: public exception { public: // +++ Constructor / Destructor +++ - InvalidSocketException(string message, int32_t socket): _message(message), _socket(socket) {} + InvalidSocketException(const string& message, int32_t socket): m_socket(socket), m_message(message) {} ~InvalidSocketException() {} public: // +++ Overrides +++ - const char* what() { return _message.c_str(); } + const char* what() const noexcept { return m_message.c_str(); } public: // +++ Getter +++ - const int32_t getSocket() const { return _socket; } + int32_t getSocket() const { return m_socket; } private: - int32_t _socket; + int32_t m_socket; - string _message; + string m_message; }; } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..12dad56 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,15 @@ +target_sources(${PROJECT_NAME} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp +) + +if (TARGET sockcanpp_test) + target_sources(sockcanpp_test + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp + ) +endif() + +add_compile_options( + -Wno-unknown-pragmas +) diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 3381a1e..7ebd83e 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -1,14 +1,11 @@ /** * @file CanDriver.cpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 Simon Cahill - * - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,18 +27,21 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include #include #include +#include ////////////////////////////// // LOCAL INCLUDES // @@ -64,83 +64,149 @@ namespace sockcanpp { using std::mutex; using std::queue; using std::string; - using std::strncpy; using std::unique_lock; + using std::unique_ptr; using std::chrono::milliseconds; - using std::this_thread::sleep_for; + using std::vector; + + namespace thread = std::this_thread; ////////////////////////////////////// // PUBLIC IMPLEMENTATION // ////////////////////////////////////// - const int32_t CanDriver::CAN_MAX_DATA_LENGTH = 8; - const int32_t CanDriver::CAN_SOCK_RAW = CAN_RAW; - const int32_t CanDriver::CAN_SOCK_SEVEN = 7; -#pragma region Object Construction - CanDriver::CanDriver(string canInterface, int32_t canProtocol, const CanId defaultSenderId): - CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) {} +#pragma region "Object Construction" + CanDriver::CanDriver(const string& canInterface, int32_t canProtocol, const CanId defaultSenderId): + CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) { } + + CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): + CanDriver(canInterface, canProtocol, filtermap_t{{0, filterMask}}, defaultSenderId) { } - CanDriver::CanDriver(const string canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): - _defaultSenderId(defaultSenderId), _canProtocol(canProtocol), _canInterface(canInterface), _canFilterMask(filterMask), _socketFd(-1) { + CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId): + m_defaultSenderId(defaultSenderId), m_canFilterMask(filters), m_canProtocol(canProtocol), m_canInterface(canInterface) { initialiseSocketCan(); } #pragma endregion -#pragma region I / O +#pragma region "I / O" /** * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. * - * @param timeout The time (in millis) to wait before timing out. + * @param timeout The time (in µsec) to wait before timing out. * * @return true If messages are available on the bus. * @return false Otherwise. */ - bool CanDriver::waitForMessages(milliseconds timeout) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + bool CanDriver::waitForMessages(microseconds timeout/* = microseconds(3000)*/) { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky(_lock); + unique_lock locky(m_lock); fd_set readFileDescriptors; - timeval waitTime; - waitTime.tv_sec = timeout.count() / 1000; - waitTime.tv_usec = timeout.count() * 1000; + timeval waitTime{0, static_cast(timeout.count())}; FD_ZERO(&readFileDescriptors); - FD_SET(_socketFd, &readFileDescriptors); - _queueSize = select(_socketFd + 1, &readFileDescriptors, 0, 0, &waitTime); + FD_SET(m_socketFd, &readFileDescriptors); + const auto fdsAvailable = select(m_socketFd + 1, &readFileDescriptors, nullptr, nullptr, &waitTime); + + int32_t bytesAvailable{0}; + const auto retCode = ioctl(m_socketFd, FIONREAD, &bytesAvailable); + if (retCode == 0) { + m_queueSize = static_cast(std::ceil(bytesAvailable / sizeof(can_frame))); + } else { + m_queueSize = 0; + // vcan interfaces don't support FIONREAD. So fall back to + // using alternate implementation in readQueuedMessages(). + m_canReadQueueSize = false; + } - return _queueSize > 0; + return fdsAvailable > 0; } + /** + * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. + * + * @param timeout The time (in millis) to wait before timing out. + * + * @return true If messages are available on the bus. + * @return false Otherwise. + */ + bool CanDriver::waitForMessages(milliseconds timeout) { return waitForMessages(std::chrono::duration_cast(timeout)); } + + /** + * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. + * + * @param timeout The time (in nanoseconds) to wait before timing out. + * + * @return true If messages are available on the bus. + * @return false Otherwise. + */ + bool CanDriver::waitForMessages(nanoseconds timeout/* = nanoseconds(3000)*/) { return waitForMessages(std::chrono::duration_cast(timeout)); } + /** * @brief Attempts to read a message from the associated CAN bus. * * @return CanMessage The message read from the bus. */ - CanMessage CanDriver::readMessage() { - return readMessageLock(); - } + CanMessage CanDriver::readMessage() { return readMessageLock(); } /** * @brief readMessage deadlock guard, attempts to read a message from the associated CAN bus. * * @return CanMessage The message read from the bus. */ - CanMessage CanDriver::readMessageLock(bool const lock) { - std::unique_ptr> _lockLck{nullptr}; - if (lock) - _lockLck = std::unique_ptr>{new std::unique_lock{_lock}}; - if (0 > _socketFd) - throw InvalidSocketException("Invalid socket!", _socketFd); - int32_t readBytes{0}; - can_frame canFrame; - memset(&canFrame, 0, sizeof(can_frame)); - readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); - if (0 > readBytes) - throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); + CanMessage CanDriver::readMessageLock(bool const lock /* = true */) { + unique_ptr> locky{nullptr}; + + if (lock) { locky = unique_ptr>{new unique_lock{m_lock}}; } + + if (0 > m_socketFd) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + can_frame canFrame{}; + + if (read(m_socketFd, &canFrame, sizeof(can_frame)) < 0) { + throw CanException( + #if __cpp_lib_format < 202002L + formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to read from CAN! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + , m_socketFd); + } + + if (m_collectTelemetry) { + // Read timestamp from the socket if available. + return CanMessage{canFrame, readFrameTimestamp()}; + } + return CanMessage{canFrame}; } + + milliseconds CanDriver::readFrameTimestamp() { + struct timeval tv{}; + if (ioctl(m_socketFd, SIOCGSTAMP, &tv) < 0) { + throw CanException( + #if __cpp_lib_format < 202002L + formatString("FAILED to read timestamp from socket! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to read timestamp from socket! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + , m_socketFd); + } + + if (!m_relativeTimestamps) { + return milliseconds(tv.tv_sec * 1000 + tv.tv_usec / 1000); + } + + // If relative timestamps are enabled, either return the delta between now and the first timestamp, or set the first timestamp + if (m_firstTimestamp.has_value()) { + auto relativeTime = milliseconds(tv.tv_sec * 1000 + tv.tv_usec / 1000) - m_firstTimestamp.value(); + return relativeTime; + } else { + m_firstTimestamp = milliseconds(tv.tv_sec * 1000 + tv.tv_usec / 1000); + return milliseconds(0); + } + } /** * @brief Attempts to send a CAN message on the associated bus. @@ -148,26 +214,40 @@ namespace sockcanpp { * @param message The message to be sent. * @param forceExtended Whether or not to force use of an extended ID. * - * @return int32_t The amount of bytes sent on the bus. + * @return ssize_t The amount of bytes sent on the bus. */ - int32_t CanDriver::sendMessage(const CanMessage message, bool forceExtended) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + ssize_t CanDriver::sendMessage(const CanMessage& message, bool forceExtended) { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky(_lockSend); + unique_lock locky(m_lockSend); - int32_t bytesWritten = 0; + ssize_t bytesWritten = 0; if (message.getFrameData().size() > CAN_MAX_DATA_LENGTH) { - throw CanException(formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH), _socketFd); + throw CanException( + #if __cpp_lib_format < 202002L + formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH) + #else + std::format("INVALID data length! Message must be smaller than {0:d} bytes!", CAN_MAX_DATA_LENGTH) + #endif // __cpp_lib_format < 202002L + , m_socketFd); } auto canFrame = message.getRawFrame(); if (forceExtended || ((uint32_t)message.getCanId() > CAN_SFF_MASK)) { canFrame.can_id |= CAN_EFF_FLAG; } - bytesWritten = write(_socketFd, (const void*)&canFrame, sizeof(canFrame)); + bytesWritten = write(m_socketFd, &canFrame, sizeof(canFrame)); - if (bytesWritten == -1) { throw CanException(formatString("FAILED to write data to socket! Error: %d => %s", errno, strerror(errno)), _socketFd); } + if (bytesWritten < 0) { + throw CanException( + #if __cpp_lib_format < 202002L + formatString("FAILED to write data to socket! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to write data to socket! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + , m_socketFd); + } return bytesWritten; } @@ -181,14 +261,40 @@ namespace sockcanpp { * * @return int32_t The total amount of bytes sent. */ - int32_t CanDriver::sendMessageQueue(queue messages, milliseconds delay, bool forceExtended) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + ssize_t CanDriver::sendMessageQueue(queue& messages, microseconds delay, bool forceExtended) { return sendMessageQueue(messages, std::chrono::duration_cast(delay), forceExtended); } + + /** + * @brief Attempts to send a queue of messages on the associated CAN bus. + * + * @param messages A queue containing the messages to be sent. + * @param delay If greater than 0, will delay the sending of the next message. + * @param forceExtended Whether or not to force use of an extended ID. + * + * @return int32_t The total amount of bytes sent. + */ + ssize_t CanDriver::sendMessageQueue(queue& messages, milliseconds delay, bool forceExtended) { return sendMessageQueue(messages, std::chrono::duration_cast(delay), forceExtended); } + + /** + * @brief Attempts to send a queue of messages on the associated CAN bus. + * + * @param messages A queue containing the messages to be sent. + * @param delay If greater than 0, will delay the sending of the next message. + * @param forceExtended Whether or not to force use of an extended ID. + * + * @return int32_t The total amount of bytes sent. + */ + ssize_t CanDriver::sendMessageQueue(queue& messages, nanoseconds delay, bool forceExtended) { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - int32_t totalBytesWritten = 0; + ssize_t totalBytesWritten = 0; while (!messages.empty()) { totalBytesWritten += sendMessage(messages.front(), forceExtended); messages.pop(); + + if (delay.count() > 0) { + thread::sleep_for(delay); + } } return totalBytesWritten; @@ -200,34 +306,169 @@ namespace sockcanpp { * @return queue A queue containing the messages read from the bus buffer. */ queue CanDriver::readQueuedMessages() { - if (_socketFd < 0) - throw InvalidSocketException("Invalid socket!", _socketFd); - unique_lock locky(_lock); - queue messages; - for (int32_t i = _queueSize; 0 < i; --i) - messages.emplace(readMessageLock(false)); + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + unique_lock locky{m_lock}; + queue messages{}; + + if (m_canReadQueueSize) { + for (int32_t i = m_queueSize; 0 < i; --i) { + messages.emplace(readMessageLock(false)); + } + } else { + // If the interface doesn't support FIONREAD, fall back + // to reading until data exhausted. + bool more{true}; + + do { + try { + messages.emplace(readMessageLock(false)); // Read a message without locking the mutex + } catch (const std::exception& e) { + more = false; + } + } while (more); + } + return messages; } + /** + * @brief Sets the CAN FD frame option for the interface. + * + * This option allows the current driver instance to receive CAN FD frames. + * + * @param enabled Whether or not to enable the CAN FD frame option. + */ + void CanDriver::allowCanFdFrames(const bool enabled/* = true*/) const { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + int32_t canFdFrames = enabled ? 1 : 0; + + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canFdFrames, sizeof(canFdFrames)) == -1) { + throw CanInitException(formatString("FAILED to set CAN FD frames on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); + } + } + + #ifdef CANXL_XLF + /** + * @brief Sets the CAN XL option for the interface. + * + * This option allows the current driver instance to send/receive CAN XL frames. + * + * @param enabled Whether or not to enable the CAN XL option. + */ + void CanDriver::allowCanXlFrames(const bool enabled/* = true*/) const { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + int32_t canXlFrames = enabled ? 1 : 0; + + + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canXlFrames, sizeof(canXlFrames)) == -1) { + throw CanInitException(formatString("FAILED to set CAN XL frames on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); + } + } + #endif // CANXL_XLF + + /** + * @brief Configures the socket to join the CAN filters. + * This is especially required, when using inverted CAN filters. + * + * Source: https://stackoverflow.com/a/57680496/2921426 + */ + void CanDriver::joinCanFilters() const { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + int32_t joinFilters = 1; + + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS, &joinFilters, sizeof(joinFilters)) == -1) { + throw CanInitException(formatString("FAILED to join CAN filters on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); + } + } + /** * @brief Attempts to set the filter mask for the associated CAN bus. * * @param mask The bit mask to apply. + * @param filterId The ID to filter on. + */ + void CanDriver::setCanFilterMask(const int32_t mask, const CanId& filterId) { setCanFilters({{filterId, static_cast(mask)}}); } + + /** + * @brief Sets multiple CAN filters for the associated CAN bus. + * + * @param filters A map containing the filters to apply. */ - void CanDriver::setCanFilterMask(const int32_t mask) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + void CanDriver::setCanFilters(const filtermap_t& filters) { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky(_lock); - can_filter canFilter; + unique_lock locky(m_lock); + vector canFilters{}; - canFilter.can_id = _defaultSenderId; - canFilter.can_mask = mask; + // Structured bindings only available with C++17 + #if __cplusplus >= 201703L + for (const auto& [id, filter] : filters) { + canFilters.push_back({*id, filter}); + } + #else + for (const auto& filterPair : filters) { + canFilters.push_back({*filterPair.first, filterPair.second}); + } + #endif - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, &canFilter, sizeof(canFilter)) == -1) { - throw CanInitException(formatString("FAILED to set CAN filter mask %x on socket %d! Error: %d => %s", mask, _socketFd, errno, strerror(errno))); + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, canFilters.data(), canFilters.size() * sizeof(can_filter)) == -1) { + throw CanInitException(formatString("FAILED to set CAN filters on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); } + } + + /** + * @brief Enables collection of advanced telemetry for the associated CAN bus. + * + * @param enabled Whether or not to enable telemetry collection. + */ + void CanDriver::setCollectTelemetry(const bool enabled/* = true*/) { m_collectTelemetry = enabled; } - _canFilterMask = mask; + /** + * @brief Sets the error filter for the associated CAN bus. + * + * @param enabled Whether or not to enable the error filter. + */ + void CanDriver::setErrorFilter(const bool enabled/* = true*/) const { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + can_err_mask_t errorMask{enabled ? CAN_ERR_MASK : 0x00}; + + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &errorMask, sizeof(can_err_mask_t)) == -1) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno)) + #else + std::format("FAILED to set CAN error filter on socket {0:d}! Error: {1:d} => {2:s}", m_socketFd, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); + } + } + + /** + * @brief Sets the receive own messages option for the associated CAN bus. + * + * This option allows the socket to receive its own messages. + * + * @param enabled Whether or not to enable the receive own messages option. + */ + void CanDriver::setReceiveOwnMessages(const bool enabled) const { + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + int32_t receiveOwnMessages = enabled ? 1 : 0; + + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &receiveOwnMessages, sizeof(receiveOwnMessages)) == -1) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to set CAN message echo on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno)) + #else + std::format("FAILED to set CAN message echo on socket {0:d}! Error: {1:d} => {2:s}", m_socketFd, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); + } } #pragma endregion @@ -241,40 +482,60 @@ namespace sockcanpp { * @brief Initialises the underlying CAN socket. */ void CanDriver::initialiseSocketCan() { - // unique_lock locky(_lock); - - struct sockaddr_can address; - struct ifreq ifaceRequest; - int64_t fdOptions = 0; - int32_t tmpReturn; - - memset(&address, 0, sizeof(sizeof(struct sockaddr_can))); - memset(&ifaceRequest, 0, sizeof(sizeof(struct ifreq))); - - _socketFd = socket(PF_CAN, SOCK_RAW, _canProtocol); - - if (_socketFd == -1) { - throw CanInitException(formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno))); + struct sockaddr_can address{}; + struct ifreq ifaceRequest{}; + int64_t fdOptions{0}; + + m_socketFd = socket(PF_CAN, SOCK_RAW, m_canProtocol); + + if (m_socketFd == -1) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to initialise socketcan! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } - strcpy(ifaceRequest.ifr_name, _canInterface.c_str()); - - if ((tmpReturn = ioctl(_socketFd, SIOCGIFINDEX, &ifaceRequest)) == -1) { - throw CanInitException(formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", _canInterface.c_str(), errno, - strerror(errno))); + std::copy(m_canInterface.begin(), m_canInterface.end(), ifaceRequest.ifr_name); + + if (ioctl(m_socketFd, SIOCGIFINDEX, &ifaceRequest) < 0) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", m_canInterface.c_str(), errno, + strerror(errno)) + #else + std::format("FAILED to perform IO control operation on socket {0:s}! Error: {1:d} => {2:s}", m_canInterface, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } - fdOptions = fcntl(_socketFd, F_GETFL); + fdOptions = fcntl(m_socketFd, F_GETFL); fdOptions |= O_NONBLOCK; - tmpReturn = fcntl(_socketFd, F_SETFL, fdOptions); + if (fcntl(m_socketFd, F_SETFL, fdOptions) < 0) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to set non-blocking mode on socket %s! Error: %d => %s", m_canInterface.c_str(), errno, strerror(errno)) + #else + std::format("FAILED to set non-blocking mode on socket {0:s}! Error: {1:d} => {2:s}", m_canInterface, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); + } address.can_family = AF_CAN; address.can_ifindex = ifaceRequest.ifr_ifindex; - setCanFilterMask(_canFilterMask); + setCanFilters(m_canFilterMask); - if ((tmpReturn = bind(_socketFd, (struct sockaddr*)&address, sizeof(address))) == -1) { - throw CanInitException(formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno))); + if (bind(m_socketFd, reinterpret_cast(&address), sizeof(address)) < 0) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to bind to socket CAN! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } } @@ -282,13 +543,34 @@ namespace sockcanpp { * @brief Closes the underlying CAN socket. */ void CanDriver::uninitialiseSocketCan() { - unique_lock locky(_lock); - - if (_socketFd <= 0) { throw CanCloseException("Cannot close invalid socket!"); } + unique_lock locky(m_lock); + + if (m_socketFd <= 0) { throw CanCloseException("Cannot close invalid socket!"); } + + if (close(m_socketFd) == -1) { + throw CanCloseException( + #if __cpp_lib_format < 202002L + formatString("FAILED to close CAN socket! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to close CAN socket! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); + } - if (close(_socketFd) == -1) { throw CanCloseException(formatString("FAILED to close CAN socket! Error: %d => %s", errno, strerror(errno))); } + m_socketFd = -1; + } - _socketFd = -1; + /** + * @brief Closes the underlying CAN socket without throwing exceptions. + */ + void CanDriver::uninitialiseSocketCanNoThrow() noexcept { + try { + uninitialiseSocketCan(); + } catch (const CanCloseException& e) { + #ifdef libsockcanpp_ENABLE_OUTPUT_ON_CLOSE_EXCEPTIONS + std::cerr << "Warning: CanCloseException caught while uninitialising CAN socket: " << e.what() << std::endl; + #endif // libsockcanpp_ENABLE_OUTPUT_ON_CLOSE_EXCEPTIONS + } } #pragma endregion diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ba94a46..7a18e6c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,29 +1,4 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.12) -project(testlibsockcanpp LANGUAGES CXX VERSION 1.0.0) -set(TARGET_NAME testsockcanpp.bin) - -set(CMAKE_CXX_STANDARD 14) - -include_directories( - include/ -) - -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) - -file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) - -add_executable(${TARGET_NAME} ${FILES}) - -target_link_libraries( - # Binary - ${TARGET_NAME} - - # Libs - sockcanpp - -lm - - -static - -static-libstdc++ - -static-libgcc -) \ No newline at end of file +add_subdirectory(app) +add_subdirectory(unit) \ No newline at end of file diff --git a/test/app/CMakeLists.txt b/test/app/CMakeLists.txt new file mode 100644 index 0000000..03938a7 --- /dev/null +++ b/test/app/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.14) + +project(testlibsockcanpp LANGUAGES CXX VERSION 1.0.0) +set(TARGET_NAME testsockcanpp.bin) + +set(CMAKE_CXX_STANDARD 14) + +include_directories( + include/ +) + +if (NOT TARGET sockcanpp) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) +endif() + +file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) + +add_executable(${TARGET_NAME} ${FILES}) + +target_link_libraries( + # Binary + ${TARGET_NAME} + + # Libs + sockcanpp_test + -lm + + -static + -static-libstdc++ + -static-libgcc +) \ No newline at end of file diff --git a/test/src/Main.cpp b/test/app/src/Main.cpp similarity index 87% rename from test/src/Main.cpp rename to test/app/src/Main.cpp index db76170..47b7ad3 100644 --- a/test/src/Main.cpp +++ b/test/app/src/Main.cpp @@ -1,6 +1,6 @@ /** * @file Main.cpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a test application using this library. * @version 0.1 * @date 2020-07-02 @@ -30,6 +30,8 @@ #include #include +using namespace std::chrono_literals; + using sockcanpp::CanDriver; using sockcanpp::CanId; using sockcanpp::exceptions::CanException; @@ -50,14 +52,15 @@ int main(int32_t argCount, char** argValues) { if (argCount > 2) { for (int32_t i = 1; i < argCount; i++) { - if (argValues[i] == "--help" || argValues[i] == "-h") { + string arg{argValues[i]}; + if (arg == "--help" || arg == "-h") { printHelp(argValues[0]); return 0; - } else if (argValues[i] == "-protocol") { + } else if (arg == "-protocol") { desiredCanSocket = atoi(argValues[i + 1]); i += 1; continue; - } else if (argValues[i] == "-iface") { + } else if (arg == "-iface") { canInterface = (argValues[i + 1]); i += 1; continue; @@ -70,12 +73,12 @@ int main(int32_t argCount, char** argValues) { if (canInterface == "") canInterface = "can0"; - CanDriver* canDriver; + CanDriver* canDriver{nullptr}; try { - canDriver = new CanDriver(canInterface, CAN_RAW); + canDriver = new CanDriver(canInterface, desiredCanSocket); } catch (CanInitException& ex) { cerr << "An error occurred while initialising CanDriver: " << ex.what() << endl; - delete canDriver; + if (canDriver) { delete canDriver; } return -1; } @@ -86,7 +89,7 @@ int main(int32_t argCount, char** argValues) { catch (InvalidSocketException& ex) { cerr << "Failed to send test message! " << ex.what() << endl; } printf("Reading messages\n"); - if (!canDriver->waitForMessages()) continue; + if (!canDriver->waitForMessages(3000ns)) continue; cout << "Reading queue..." << endl; auto canMessages = canDriver->readQueuedMessages(); diff --git a/test/unit/.gitignore b/test/unit/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/test/unit/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt new file mode 100644 index 0000000..bfc08a4 --- /dev/null +++ b/test/unit/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) + +project(libsockcanpp_unittests LANGUAGES CXX VERSION 0.1) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(GTest REQUIRED) + +include_directories( + # none as of now +) + +if (NOT TARGET sockcanpp) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) +endif() + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) + +add_compile_options( + -Wall + -Wextra + -Wpedantic + -Werror + -Wno-unknown-pragmas +) + +add_executable(${PROJECT_NAME} ${SOURCES}) + +target_link_libraries( + ${PROJECT_NAME} + + sockcanpp_test + ${GTEST_LIBRARIES} + pthread +) + +gtest_discover_tests(${PROJECT_NAME}) \ No newline at end of file diff --git a/test/unit/src/CanId_Tests.cpp b/test/unit/src/CanId_Tests.cpp new file mode 100644 index 0000000..deed8e8 --- /dev/null +++ b/test/unit/src/CanId_Tests.cpp @@ -0,0 +1,325 @@ +/** + * @file CanId_Tests.cpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains all the unit tests for the CanId structure. + * @version 0.1 + * @date 2024-05-29 + * + * @copyright Copyright (c) 2024 Simon Cahill and Contributors. + */ + +#include + +#include + +using sockcanpp::CanId; + +using std::string; + +TEST(CanIdTests, CanId_invalidId_ExpectFalse) { + ASSERT_FALSE(CanId::isValidIdentifier(-1)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId) { + ASSERT_TRUE(CanId::isValidIdentifier(0x123)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId) { + ASSERT_TRUE(CanId::isValidIdentifier(0x123456)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCast) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCast) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCast) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCast) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToInt16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((int16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToUint16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((uint16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToInt32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToInt16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToUint16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToInt32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToInt16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((int16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToUint16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((uint16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToInt32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToInt16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToUint16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToInt32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToUint32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((uint32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToUint32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((uint32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToUint32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToUint32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue) { + auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isErrorFrame(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse) { + auto id = 0x123; + ASSERT_FALSE(CanId::isErrorFrame(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue_ExplicitCast) { + auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isErrorFrame((int32_t)id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse_ExplicitCast) { + auto id = 0x123; + ASSERT_FALSE(CanId::isErrorFrame((int32_t)id)); +} + +TEST(CanIdTests, CanId_isExtendedFrame_ExpectTrue) { + auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isExtendedFrame(id)); +} + +TEST(CanIdTests, CanId_isExtendedFrame_ExpectFalse) { + auto id = 0x123; + ASSERT_FALSE(CanId::isExtendedFrame(id)); +} + +TEST(CanIdTests, CanId_isRtr_ExpectTrue) { + auto id = 0x40000000; + ASSERT_TRUE(CanId::isRemoteTransmissionRequest(id)); +} + +TEST(CanIdTests, CanId_isRtr_ExpectFalse) { + auto id = 0x123; + ASSERT_FALSE(CanId::isRemoteTransmissionRequest(id)); +} + +// Test constexpr +TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue_Constexpr) { + constexpr auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isErrorFrame(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse_Constexpr) { + constexpr auto id = 0x123; + ASSERT_FALSE(CanId::isErrorFrame(id)); +} + +// Test implicit operators +// Implicit operators strip out control bits + +TEST(CanIdTests, CanId_ImplicitOperatorInt32_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((int32_t)id, 0x123); +} + +TEST(CanIdTests, CanId_ImplicitOperatorInt16_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((int16_t)id, 0x123); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint16_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((uint16_t)id, 0x123); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint32_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((uint32_t)id, 0x123); +} + +// Test implicit operators with control bits set; these should be stripped. +TEST(CanIdTests, CanId_ImplicitOperatorInt32_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((int32_t)id, 0x12345678); +} + +TEST(CanIdTests, CanId_ImplicitOperatorInt16_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((int16_t)id, 0x5678); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint16_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((uint16_t)id, 0x5678); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint32_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((uint32_t)id, 0x12345678); +} + +// Test arithmetic operators +TEST(CanIdTests, CanId_ArithmeticOperatorPlus_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id + 0x123, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorMinus_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id - 0x123, 0); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorPlusEquals_ExpectTrue) { + CanId id(0x123); + id += 0x123; + ASSERT_EQ(id, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorMinusEquals_ExpectTrue) { + CanId id(0x123); + id -= 0x123; + ASSERT_EQ(id, 0); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorStar_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id * 2, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorSlash_ExpectTrue) { + CanId id(0x246); + ASSERT_EQ(id / 2, 0x123); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorStarEquals_ExpectTrue) { + CanId id(0x123); + id *= 2; + ASSERT_EQ(id, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorSlashEquals_ExpectTrue) { + CanId id(0x246); + id /= 2; + ASSERT_EQ(id, 0x123); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorModulo_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id % 2, 1); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorModuloEquals_ExpectTrue) { + CanId id(0x123); + id %= 2; + ASSERT_EQ(id, 1); +} + +#if __cpp_concepts >= 201907 + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue) { + string id{"0x123"}; + + EXPECT_NO_THROW(CanId canId(id)); + EXPECT_NO_THROW(CanId canId{id}); + EXPECT_NO_THROW(CanId canId = id; (void)canId;); + EXPECT_NO_THROW(CanId canId{"0x123"}); + + CanId canId(id); + ASSERT_EQ(canId, 0x123); +} + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue_ExplicitCast) { + string id{"0x123"}; + + EXPECT_NO_THROW(CanId canId(static_cast(id.c_str()))); + EXPECT_NO_THROW(CanId canId = static_cast(id.c_str()); (void)canId;); + EXPECT_NO_THROW(CanId canId = static_cast(id.c_str()); (void)canId;); + + CanId canId(static_cast(id.c_str())); + ASSERT_EQ(canId, 0x123); +} + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue_ImplicitCast) { + string id{"0x123"}; + + EXPECT_NO_THROW(CanId canId = id; (void)canId;); + EXPECT_NO_THROW(CanId canId = id; (void)canId;); + + CanId canId = id; + ASSERT_EQ(canId, 0x123); +} + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectError) { + string id{"hello_world"}; + + EXPECT_THROW(CanId canId(id), std::invalid_argument); + EXPECT_THROW(CanId canId = id; (void)canId;, std::invalid_argument); + EXPECT_THROW(CanId canId = id; (void)canId;, std::invalid_argument); +} + +#endif // __cpp_concepts >= 201907 \ No newline at end of file diff --git a/test/unit/src/CanMessage_ErrorFrameTests.cpp b/test/unit/src/CanMessage_ErrorFrameTests.cpp new file mode 100644 index 0000000..d707b5c --- /dev/null +++ b/test/unit/src/CanMessage_ErrorFrameTests.cpp @@ -0,0 +1,129 @@ +/** + * @file CanMessage_ErrorFrameTests.cpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains all the unit tests for the error frame handling in the CanMessage structure. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#include + +#include + +#include + +using sockcanpp::CanId; +using sockcanpp::CanMessage; +using namespace sockcanpp::can_errors; + +using std::array; +using std::string; + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectTxTimeout) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_TX_TIMEOUT), ""); + + ASSERT_TRUE(msg.isTxTimeout()); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_Bit1) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), "\x01"); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), 1); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_Bit10) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), "\x0a"); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), 10); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_Bit100) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), "\x64"); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), 100); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_TestAllBits) { + for (uint8_t i = 0; i < 0xff; i++) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), string(1, i)); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), i); + } +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectUnspecified) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x00"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::UNSPECIFIED_ERROR); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRxOverflow) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x01"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECEIVE_OVERFLOW); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectTxOverflow) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x02"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::TRANSMIT_OVERFLOW); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRxWarning) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x04"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECEIVE_WARNING); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectTxWarning) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x08"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::TRANSMIT_WARNING); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRxPassive) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x10"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECEIVE_PASSIVE); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectTxPassive) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x20"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::TRANSMIT_PASSIVE); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRecoveredActive) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x40"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECOVERED_ACTIVE); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ProtocolError_ExpectCombinations) { + array protType{ 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; + array protLocation{ 0x00, 0x03, 0x02, 0x06, 0x04, 0x05, 0x07, 0x0F, 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x0A, 0x08, 0x18, 0x19, 0x1B, 0x1A, 0x12 }; + + for (const auto i : protType) { + for (const auto j : protLocation) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_PROT), string({ static_cast(0xff), static_cast(0xff), static_cast(i), static_cast(j) })); + + ASSERT_TRUE(msg.hasProtocolViolation()); + ASSERT_EQ(msg.getProtocolError().errorCode, static_cast(i)); + ASSERT_EQ(msg.getProtocolError().errorLocation, static_cast(j)); + } + } +} diff --git a/test/unit/src/CanMessage_Tests.cpp b/test/unit/src/CanMessage_Tests.cpp new file mode 100644 index 0000000..772e1ab --- /dev/null +++ b/test/unit/src/CanMessage_Tests.cpp @@ -0,0 +1,61 @@ +/** + * @file CanMessage_Tests.cpp + * @author Simon Cahill (s.cahill@procyon-systems.de) + * @brief Contains all the unit tests for the CanMessage structure. + * @version 0.1 + * @date 2025-07-30 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#include + +#include + +using sockcanpp::CanMessage; + +using std::string; + +TEST(CanMessageTests, CanMessage_Constructor_ExpectDefaultValues) { + CanMessage msg; + + ASSERT_EQ(msg.getCanId(), 0); + ASSERT_EQ(msg.getFrameData(), ""); + ASSERT_TRUE(msg.getRawFrame().can_id == 0 && msg.getRawFrame().can_dlc == 0); +} + +TEST(CanMessageTests, CanMessage_ConstructorWithId_ExpectCorrectId) { + CanMessage msg(0x123, ""); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), ""); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 0); +} + +TEST(CanMessageTests, CanMessage_ConstructorWithId_ExpectCorrectIdAndTestData) { + CanMessage msg(0x123, "TestData"); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), "TestData"); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 8); +} + +TEST(CanMessageTests, CanMessage_ConstructorWithIdAndTimestamp_ExpectCorrectValues) { + auto timestamp = std::chrono::milliseconds(100); + CanMessage msg(0x123, "TestData", timestamp); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), "TestData"); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 8); + ASSERT_EQ(msg.getTimestampOffset(), timestamp); +} + +#if __cpp_lib_span >= 202002L +TEST(CanMessageTests, CanMessage_ConstructorWithIdAndSpan_ExpectCorrectValues) { + CanMessage msg(0x123, std::span(reinterpret_cast("TestData"), 8)); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), "TestData"); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 8); +} +#endif // __cpp_lib_span >= 202002L \ No newline at end of file diff --git a/test/unit/src/main.cpp b/test/unit/src/main.cpp new file mode 100644 index 0000000..49c7a31 --- /dev/null +++ b/test/unit/src/main.cpp @@ -0,0 +1,16 @@ +/** + * @file main.cpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the main entry point for the unit tests. + * @version 0.1 + * @date 2024-05-29 + * + * @copyright Copyright (c) 2024 Simon Cahill and Contributors. + */ + +#include + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/toolchains/x86_64_llvm_toolchain.cmake b/toolchains/x86_64_llvm_toolchain.cmake new file mode 100644 index 0000000..c72bb41 --- /dev/null +++ b/toolchains/x86_64_llvm_toolchain.cmake @@ -0,0 +1,16 @@ +######################################################################### +# ___ __ __ _ _ _ _ __ ____ __ # +# __ _( _ ) / / / /| | | | | | |\ \ / / \/ | # +# \ \ / _ \/ _ \ / _ \_ _| | |__| |_\ V /| |\/| | # +# /_\_\___/\___/_\___/ |_| |____|____\_/ |_| |_| # +# |___| # +######################################################################### + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +### +# Configure compiler suite +### +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) \ No newline at end of file