diff --git a/.clang-format b/.clang-format index e553c79..350cdc2 100644 --- a/.clang-format +++ b/.clang-format @@ -44,6 +44,8 @@ BraceWrapping: AfterClass: false AfterCaseLabel: false +InsertBraces: true + # CrlInstruction *a; DerivePointerAlignment: false PointerAlignment: Left @@ -102,3 +104,6 @@ AllowShortLambdasOnASingleLine: Empty # We do not want clang-format to put all arguments on a new line AllowAllArgumentsOnNextLine: false + +InsertNewlineAtEOF: true +LineEnding: LF diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..3f85e19 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,169 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,clang-analyzer-*,clang-analyzer-cplusplus*,cert-*,cppcoreguidelines-*,portability-*,readability-*,clang-diagnostic-*,misc-include-cleaner' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: none +User: dwag +CheckOptions: + readability-simplify-subscript-expr.Types: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' + readability-suspicious-call-argument.PrefixSimilarAbove: '30' + modernize-replace-auto-ptr.IncludeStyle: llvm + cppcoreguidelines-no-malloc.Reallocations: '::realloc' + cppcoreguidelines-owning-memory.LegacyResourceConsumers: '::free;::realloc;::freopen;::fclose' + readability-static-accessed-through-instance.NameSpecifierNestingThreshold: '3' + readability-simplify-boolean-expr.SimplifyDeMorgan: 'true' + readability-function-size.VariableThreshold: '4294967295' + cert-dcl16-c.NewSuffixes: 'L;LL;LU;LLU' + cert-dcl51-cpp.AggressiveDependentMemberLookup: 'false' + readability-identifier-naming.GetConfigPerFile: 'true' + cert-err61-cpp.MaxSize: '10' + cert-sig30-c.AsyncSafeFunctionSet: POSIX + readability-inconsistent-declaration-parameter-name.Strict: 'false' + cppcoreguidelines-macro-usage.CheckCapsOnly: 'false' + readability-suspicious-call-argument.DiceDissimilarBelow: '60' + cert-dcl37-c.AllowedIdentifiers: '' + readability-function-size.NestingThreshold: '4294967295' + cppcoreguidelines-narrowing-conversions.IgnoreConversionFromTypes: '' + readability-function-size.ParameterThreshold: '4294967295' + readability-suspicious-call-argument.Equality: 'true' + readability-function-cognitive-complexity.IgnoreMacros: 'false' + cert-str34-c.DiagnoseSignedUnsignedCharComparisons: 'false' + cert-err33-c.CheckedFunctions: '::aligned_alloc;::asctime_s;::at_quick_exit;::atexit;::bsearch;::bsearch_s;::btowc;::c16rtomb;::c32rtomb;::calloc;::clock;::cnd_broadcast;::cnd_init;::cnd_signal;::cnd_timedwait;::cnd_wait;::ctime_s;::fclose;::fflush;::fgetc;::fgetpos;::fgets;::fgetwc;::fopen;::fopen_s;::fprintf;::fprintf_s;::fputc;::fputs;::fputwc;::fputws;::fread;::freopen;::freopen_s;::fscanf;::fscanf_s;::fseek;::fsetpos;::ftell;::fwprintf;::fwprintf_s;::fwrite;::fwscanf;::fwscanf_s;::getc;::getchar;::getenv;::getenv_s;::gets_s;::getwc;::getwchar;::gmtime;::gmtime_s;::localtime;::localtime_s;::malloc;::mbrtoc16;::mbrtoc32;::mbsrtowcs;::mbsrtowcs_s;::mbstowcs;::mbstowcs_s;::memchr;::mktime;::mtx_init;::mtx_lock;::mtx_timedlock;::mtx_trylock;::mtx_unlock;::printf_s;::putc;::putwc;::raise;::realloc;::remove;::rename;::scanf;::scanf_s;::setlocale;::setvbuf;::signal;::snprintf;::snprintf_s;::sprintf;::sprintf_s;::sscanf;::sscanf_s;::strchr;::strerror_s;::strftime;::strpbrk;::strrchr;::strstr;::strtod;::strtof;::strtoimax;::strtok;::strtok_s;::strtol;::strtold;::strtoll;::strtoul;::strtoull;::strtoumax;::strxfrm;::swprintf;::swprintf_s;::swscanf;::swscanf_s;::thrd_create;::thrd_detach;::thrd_join;::thrd_sleep;::time;::timespec_get;::tmpfile;::tmpfile_s;::tmpnam;::tmpnam_s;::tss_create;::tss_get;::tss_set;::ungetc;::ungetwc;::vfprintf;::vfprintf_s;::vfscanf;::vfscanf_s;::vfwprintf;::vfwprintf_s;::vfwscanf;::vfwscanf_s;::vprintf_s;::vscanf;::vscanf_s;::vsnprintf;::vsnprintf_s;::vsprintf;::vsprintf_s;::vsscanf;::vsscanf_s;::vswprintf;::vswprintf_s;::vswscanf;::vswscanf_s;::vwprintf_s;::vwscanf;::vwscanf_s;::wcrtomb;::wcschr;::wcsftime;::wcspbrk;::wcsrchr;::wcsrtombs;::wcsrtombs_s;::wcsstr;::wcstod;::wcstof;::wcstoimax;::wcstok;::wcstok_s;::wcstol;::wcstold;::wcstoll;::wcstombs;::wcstombs_s;::wcstoul;::wcstoull;::wcstoumax;::wcsxfrm;::wctob;::wctrans;::wctype;::wmemchr;::wprintf_s;::wscanf;::wscanf_s;' + cppcoreguidelines-explicit-virtual-functions.AllowOverrideAndFinal: 'false' + readability-redundant-smartptr-get.IgnoreMacros: 'true' + readability-identifier-naming.AggressiveDependentMemberLookup: 'false' + readability-const-return-type.IgnoreMacros: 'true' + cert-err61-cpp.WarnOnLargeObjects: 'false' + cert-err09-cpp.WarnOnLargeObjects: 'false' + readability-identifier-length.MinimumParameterNameLength: '3' + cppcoreguidelines-owning-memory.LegacyResourceProducers: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' + cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion: 'true' + cppcoreguidelines-init-variables.IncludeStyle: llvm + readability-avoid-const-params-in-decls.IgnoreMacros: 'true' + google-readability-namespace-comments.ShortNamespaceLines: '10' + readability-suspicious-call-argument.JaroWinklerDissimilarBelow: '75' + readability-suspicious-call-argument.SuffixSimilarAbove: '30' + readability-suspicious-call-argument.Suffix: 'true' + cert-err09-cpp.MaxSize: '10' + cppcoreguidelines-narrowing-conversions.WarnOnIntegerNarrowingConversion: 'true' + cppcoreguidelines-prefer-member-initializer.UseAssignment: 'false' + cert-oop57-cpp.MemSetNames: '' + cppcoreguidelines-explicit-virtual-functions.FinalSpelling: final + readability-function-cognitive-complexity.DescribeBasicIncrements: 'true' + readability-suspicious-call-argument.MinimumIdentifierNameLength: '3' + modernize-loop-convert.NamingStyle: CamelCase + cppcoreguidelines-pro-type-member-init.UseAssignment: 'false' + readability-inconsistent-declaration-parameter-name.IgnoreMacros: 'true' + readability-suspicious-call-argument.SubstringDissimilarBelow: '40' + readability-identifier-naming.IgnoreFailedSplit: 'false' + modernize-pass-by-value.IncludeStyle: llvm + readability-qualified-auto.AddConstToQualified: 'true' + cppcoreguidelines-explicit-virtual-functions.OverrideSpelling: override + readability-simplify-boolean-expr.ChainedConditionalReturn: 'false' + readability-else-after-return.WarnOnConditionVariables: 'true' + readability-uppercase-literal-suffix.IgnoreMacros: 'true' + modernize-use-nullptr.NullMacros: 'NULL' + cert-dcl59-cpp.HeaderFileExtensions: ';h;hh;hpp;hxx' + readability-suspicious-call-argument.SuffixDissimilarBelow: '25' + cppcoreguidelines-macro-usage.AllowedRegexp: '^DEBUG_*' + readability-suspicious-call-argument.LevenshteinSimilarAbove: '66' + cppcoreguidelines-narrowing-conversions.PedanticMode: 'false' + readability-suspicious-call-argument.Levenshtein: 'true' + readability-suspicious-call-argument.JaroWinkler: 'true' + readability-suspicious-call-argument.Prefix: 'true' + readability-simplify-boolean-expr.ChainedConditionalAssignment: 'false' + cppcoreguidelines-avoid-magic-numbers.IgnoreAllFloatingPointValues: 'false' + cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions: 'false' + cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField: 'false' + cert-err09-cpp.CheckThrowTemporaries: 'true' + cert-dcl51-cpp.Invert: 'false' + readability-function-size.LineThreshold: '4294967295' + modernize-loop-convert.MaxCopySize: '16' + readability-suspicious-call-argument.PrefixDissimilarBelow: '25' + portability-simd-intrinsics.Suggest: 'false' + cppcoreguidelines-pro-bounds-constant-array-index.GslHeader: '' + cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors: 'false' + readability-redundant-string-init.StringNames: '::std::basic_string_view;::std::basic_string' + readability-magic-numbers.IgnoreBitFieldsWidths: 'true' + readability-braces-around-statements.ShortStatementLines: '0' + readability-suspicious-call-argument.LevenshteinDissimilarBelow: '50' + readability-magic-numbers.IgnoredFloatingPointValues: '1.0;100.0;' + readability-redundant-declaration.IgnoreMacros: 'true' + readability-implicit-bool-conversion.AllowPointerConditions: 'false' + readability-identifier-length.IgnoredExceptionVariableNames: '^[e]$' + google-readability-braces-around-statements.ShortStatementLines: '1' + portability-restrict-system-includes.Includes: '*' + cppcoreguidelines-pro-type-member-init.IgnoreArrays: 'false' + readability-else-after-return.WarnOnUnfixable: 'true' + cert-dcl51-cpp.AllowedIdentifiers: '' + cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues: '1.0;100.0;' + cert-oop57-cpp.MemCpyNames: '' + readability-suspicious-call-argument.SubstringSimilarAbove: '50' + cppcoreguidelines-macro-usage.IgnoreCommandLineMacros: 'true' + readability-suspicious-call-argument.Substring: 'true' + cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle: llvm + cppcoreguidelines-narrowing-conversions.WarnWithinTemplateInstantiation: 'false' + readability-identifier-length.IgnoredVariableNames: '' + readability-magic-numbers.IgnoreAllFloatingPointValues: 'false' + readability-identifier-length.MinimumVariableNameLength: '3' + readability-suspicious-call-argument.Abbreviations: 'arr=array;cnt=count;idx=index;src=source;stmt=statement;cpy=copy;dest=destination;dist=distancedst=distance;ptr=pointer;wdth=width;str=string;ln=line;srv=server;attr=attribute;ref=reference;buf=buffer;col=column;nr=number;vec=vector;len=length;elem=element;val=value;i=index;var=variable;hght=height;cl=client;num=number;pos=position;lst=list;addr=address' + cppcoreguidelines-narrowing-conversions.WarnOnEquivalentBitWidth: 'true' + cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues: '1;2;3;4;' + cppcoreguidelines-non-private-member-variables-in-classes.IgnorePublicMemberVariables: 'false' + modernize-loop-convert.MinConfidence: reasonable + readability-uniqueptr-delete-release.PreferResetCall: 'false' + cppcoreguidelines-avoid-magic-numbers.IgnorePowersOf2IntegerValues: 'false' + readability-identifier-length.MinimumExceptionNameLength: '2' + cert-oop57-cpp.MemCmpNames: '' + google-readability-namespace-comments.SpacesBeforeComments: '2' + cppcoreguidelines-avoid-magic-numbers.IgnoreBitFieldsWidths: 'true' + cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic: 'true' + readability-uppercase-literal-suffix.NewSuffixes: '' + readability-function-cognitive-complexity.Threshold: '25' + cppcoreguidelines-no-malloc.Allocations: '::malloc;::calloc' + readability-simplify-boolean-expr.SimplifyDeMorganRelaxed: 'false' + cppcoreguidelines-narrowing-conversions.WarnOnIntegerToFloatingPointNarrowingConversion: 'true' + readability-function-size.BranchThreshold: '4294967295' + cert-err61-cpp.CheckThrowTemporaries: 'true' + readability-implicit-bool-conversion.AllowIntegerConditions: 'false' + readability-identifier-length.IgnoredParameterNames: '^[n]$' + readability-function-size.StatementThreshold: '800' + readability-identifier-naming.IgnoreMainLikeFunctions: 'false' + llvm-qualified-auto.AddConstToQualified: 'false' + cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted: 'false' + cppcoreguidelines-init-variables.MathHeader: '' + cert-str34-c.CharTypdefsToIgnore: '' + google-readability-function-size.StatementThreshold: '800' + llvm-else-after-return.WarnOnConditionVariables: 'false' + cert-msc51-cpp.DisallowedSeedTypes: 'time_t,std::time_t' + readability-suspicious-call-argument.DiceSimilarAbove: '70' + readability-suspicious-call-argument.Dice: 'true' + readability-suspicious-call-argument.Abbreviation: 'true' + cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor: 'true' + readability-identifier-length.IgnoredLoopCounterNames: '^[ijk_]$' + cert-dcl37-c.Invert: 'false' + cert-dcl37-c.AggressiveDependentMemberLookup: 'false' + readability-identifier-length.MinimumLoopCounterNameLength: '2' + cppcoreguidelines-avoid-do-while.IgnoreMacros: 'false' + cert-msc54-cpp.AsyncSafeFunctionSet: POSIX + cppcoreguidelines-no-malloc.Deallocations: '::free' + readability-magic-numbers.IgnoredIntegerValues: '1;2;3;4;' + readability-magic-numbers.IgnorePowersOf2IntegerValues: 'false' + portability-simd-intrinsics.Std: '' + readability-redundant-member-init.IgnoreBaseInCopyConstructors: 'false' + readability-suspicious-call-argument.JaroWinklerSimilarAbove: '85' + cert-dcl16-c.IgnoreMacros: 'true' + llvm-else-after-return.WarnOnUnfixable: 'false' + cert-msc32-c.DisallowedSeedTypes: 'time_t,std::time_t' + readability-identifier-naming.NamespaceCase: 'CamelCase' + readability-identifier-naming.UnionCase: 'CamelCase' + readability-identifier-naming.ClassCase: 'CamelCase' + readability-identifier-naming.ClassMemberCase: 'lower_case' + readability-identifier-naming.ClassMethodCase: 'lower_case' + readability-identifier-naming.StructCase: 'CamelCase' + readability-identifier-naming.MemberCase: 'lower_case' + readability-identifier-naming.MethodCase: 'lower_case' + readability-identifier-naming.FunctionCase: 'lower_case' +... + diff --git a/.github/data/release_body.md b/.github/data/release_body.md new file mode 100644 index 0000000..df96e6d --- /dev/null +++ b/.github/data/release_body.md @@ -0,0 +1,19 @@ +# Libdbc release + +### Developer Notes + +**TODO: Update this!** + +## Breaking Changes + +* + +## Features + +* + +## Bugs + +* + + diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml new file mode 100644 index 0000000..3f96f40 --- /dev/null +++ b/.github/workflows/pipeline.yml @@ -0,0 +1,231 @@ +name: Libdbc Pipeline + +on: [push, pull_request, workflow_call, workflow_dispatch] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + linux-builds: + name: linux ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} + runs-on: ubuntu-22.04 + strategy: + fail-fast: false + matrix: + cxx: + - g++-11 + - clang++-16 + build_type: [Debug, Release] + std: [11] + include: + - cxx: g++-11 + cc: gcc-11 + - cxx: clang++-16 + cc: clang-16 + llvm_version: 16 + + env: + BUILD_TYPE: ${{matrix.build_type}} + + steps: + - uses: actions/checkout@v4 + + - name: Install clang + if: ${{ matrix.llvm_version }} + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh ${{ matrix.llvm_version }} + + - name: Prepare environment + run: | + sudo apt-get update + sudo apt-get install -y locales + + sudo locale-gen de_DE.UTF-8 + + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" + + - name: Print gcc version + run: gcc --version + + - name: Configure build + env: + CC: ${{matrix.cc}} + CXX: ${{matrix.cxx}} + run: | + cmake -Bbuild -H$GITHUB_WORKSPACE \ + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_CXX_EXTENSIONS=ON \ + -DDBC_TEST_LOCALE_INDEPENDENCE=ON \ + -DDBC_GENERATE_SINGLE_HEADER=ON + + - name: Build tests + lib + run: cmake --build build --parallel `nproc` + + - name: Run tests + timeout-minutes: 3 + env: + CTEST_OUTPUT_ON_FAILURE: 1 + run: ctest --output-on-failure --test-dir build -j `nproc` + + - name: Run install + run: cmake --install build --prefix . + + + windows-build: + name: ${{matrix.os}}, ${{matrix.std}}, ${{matrix.build_type}}, ${{matrix.platform}} + runs-on: ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: [windows-2025, windows-2022] + platform: [Win32, x64] + build_type: [Debug, Release] + std: [11] + + env: + BUILD_TYPE: ${{matrix.build_type}} + + steps: + - uses: actions/checkout@v4 + + - name: Configure build + working-directory: ${{runner.workspace}} + run: | + cmake -S $Env:GITHUB_WORKSPACE ` + -B ${{runner.workspace}}/build ` + -A ${{matrix.platform}} ` + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} ` + -DCMAKE_CXX_STANDARD=${{matrix.std}} ` + -DCMAKE_CXX_STANDARD_REQUIRED=ON ` + -DCMAKE_CXX_EXTENSIONS=ON ` + -DDBC_TEST_LOCALE_INDEPENDENCE=ON + + - name: Build tests + lib + working-directory: ${{runner.workspace}} + run: cmake --build build --config ${{matrix.build_type}} --parallel %NUMBER_OF_PROCESSORS% + shell: cmd + + - name: Run tests + timeout-minutes: 3 + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}} + run: ctest --output-on-failure --test-dir build -j %NUMBER_OF_PROCESSORS% + shell: cmd + + macos-builds: + name: ${{matrix.os}}, ${{matrix.cxx}}, C++${{matrix.std}}, ${{matrix.build_type}} + runs-on: ${{matrix.os}} + strategy: + fail-fast: false + matrix: + os: [macos-15-large] + cxx: + - g++ + - clang++ + build_type: [Debug, Release] + std: [11] + include: + - cxx: g++ + cc: gcc + - cxx: clang++ + cc: clang + + env: + BUILD_TYPE: ${{matrix.build_type}} + + steps: + - uses: actions/checkout@v4 + + - name: Configure build + working-directory: ${{runner.workspace}} + env: + CC: ${{matrix.cc}} + CXX: ${{matrix.cxx}} + run: | + cmake -Bbuild -H$GITHUB_WORKSPACE \ + -DCMAKE_BUILD_TYPE=${{matrix.build_type}} \ + -DCMAKE_CXX_STANDARD=${{matrix.std}} \ + -DCMAKE_CXX_STANDARD_REQUIRED=ON \ + -DCMAKE_CXX_EXTENSIONS=ON \ + -DDBC_TEST_LOCALE_INDEPENDENCE=ON + + - name: Build tests + lib + working-directory: ${{runner.workspace}} + run: cmake --build build --parallel `sysctl -n hw.ncpu` + + - name: Run tests + timeout-minutes: 3 + env: + CTEST_OUTPUT_ON_FAILURE: 1 + working-directory: ${{runner.workspace}} + run: ctest --output-on-failure --test-dir build -j `sysctl -n hw.ncpu` -V + + format-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install clang-format version + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 17 + + sudo apt update && sudo apt install -y clang-format-17 + sudo ln -sf $(which clang-format-17) $(which clang-format) + + test "$(clang-format --version)" == "$(clang-format-17 --version)" + + - name: Test format with clang format + run: ./scripts/fmt.sh + + clang-tidy-check: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Install clang-tidy version + run: | + wget https://apt.llvm.org/llvm.sh + chmod +x llvm.sh + sudo ./llvm.sh 17 + + sudo apt update && sudo apt install -y clang-tidy-17 + sudo ln -sf $(which clang-tidy-17) $(which clang-tidy) + + test "$(clang-tidy --version)" == "$(clang-tidy-17 --version)" + + - name: Make build directory + run: cmake -Bbuild -H$GITHUB_WORKSPACE + + - name: Run clang-tidy check + run: cmake --build build --target clang-tidy-check + + check-against-minimum-cmake: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Cmake install + run: | + wget https://github.com/Kitware/CMake/releases/download/v3.16.9/cmake-3.16.9-linux-x86_64.sh + chmod +x cmake-3.16.9-linux-x86_64.sh + sudo ./cmake-3.16.9-linux-x86_64.sh --skip-license --prefix=/usr/local + cmake --version + + - name: Make build directory + run: cmake -Bbuild -H$GITHUB_WORKSPACE + + - name: Run build + run: cmake --build build + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9d76a79..0e45a26 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,27 +1,87 @@ name: Libdbc Release +run-name: Libdbc release v${{ inputs.major }}.${{ inputs.minor }}.${{ inputs.patch }} as release type ${{ inputs.release_type }} on: workflow_dispatch: inputs: major: required: true - type: string + description: "The major version" + type: number minor: required: true - type: string + description: "The minor version" + type: number patch: required: true - type: string + description: "The patch version" + type: number + + release_type: + type: choice + description: "The type of release you are making. Controls branch naming / creation" + options: + - patch + - minor + - major jobs: + check_version: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: "Validate version in cmake before continuing" + run: ./scripts/check_version.py --version "v${{ inputs.major }}.${{ inputs.minor }}.${{ inputs.patch }}" + + pipeline: + needs: [check_version] + uses: ./.github/workflows/pipeline.yml + create_release: runs-on: ubuntu-latest + needs: [pipeline] + + env: + header_file_path: build/single_header/libdbc/libdbc.hpp + steps: - - name: "Checkout the code" - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + + - name: "Setup Cargo" + run: | + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y + source "$HOME/.cargo/env" + + - name: Configure build + run: cmake -Bbuild -H$GITHUB_WORKSPACE -DDBC_GENERATE_SINGLE_HEADER=ON + + - name: Generate the header file + run: cmake --build build --parallel `nproc` --target single_header + + - uses: actions/upload-artifact@v4 + with: + if-no-files-found: error + name: header-only + path: ${{ env.header_file_path }} + + - name: "Create a branch if we are making a major / minor release" + uses: peterjgrainger/action-create-branch@v2.2.0 + if: ${{ inputs.release_type }} == "minor" || ${{ inputs.release_type }} == "major" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + branch: 'release/v${{ inputs.major }}.${{ inputs.minor }}.X' + sha: '${{ github.sha }}' - - name: "Run tests as a pre check" - uses: .github/workflows/tests.yml + - uses: ncipollo/release-action@v1 + with: + artifacts: "${{ env.header_file_path }}" + draft: true + bodyFile: ".github/data/release_body.md" + tag: v${{ inputs.major }}.${{ inputs.minor }}.${{ inputs.patch }} + commit: release/v${{ inputs.major }}.${{ inputs.minor }}.X diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index dd213b5..0000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Libdbc Tests - -on: - push: - branches: - - "master" - pull_request: - workflow_dispatch: - -env: - # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release - -jobs: - build-and-test: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Configure CMake - run: cmake -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Bbuild -H. - - - name: Build the library - run: | - cd build - make -j$(nproc) - - - name: Run unit tests - run: | - cd build - ctest --output-on-failure - - format-check: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - name: Test format with clang format - run: ./scripts/fmt.sh - diff --git a/.gitignore b/.gitignore index 781c60c..fcb11cc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,20 @@ # Build folders bin/ build/ +Testing/ # -- Git -- # -*.bak \ No newline at end of file +*.bak + +# -- Python -- # +venv*/ +__pycache__/ + +# -- IDEs / Editors -- # +.idea/ +.vscode/ +*.sublime-* + +# -- Static Analyzers -- # +.cache/ + diff --git a/CMakeLists.txt b/CMakeLists.txt index 44e6cef..551af92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,109 +1,125 @@ -cmake_minimum_required(VERSION 3.16) - -project(dbc VERSION 0.1.1 DESCRIPTION "C++ DBC Parser") - -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# package -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_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE) -set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) -include(CPack) - -option(DEBUG "use debug flag" NO) -option(ENABLE_TESTS "Enable Unittests" ON) - -# defines variables used in the dbc.pc.in -include(GNUInstallDirs) - -# specify the C++ standard -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED True) - -find_package(FastFloat QUIET) -if (NOT ${FastFloat_FOUND}) - include(FetchContent) - FetchContent_Declare( - FastFloat - GIT_REPOSITORY https://github.com/fastfloat/fast_float.git - GIT_TAG 1ea4f27b2aeee2859a1354a3c24cff52a116cad1 - ) - FetchContent_MakeAvailable(FastFloat) -endif() - -set(GCC_COMPILE_FLAGS "-Wextra -Wall -Wfloat-equal -Wundef -Wshadow \ --Wpointer-arith -Wcast-align -Wstrict-prototypes -Wwrite-strings \ --Waggregate-return -Wcast-qual -Wswitch-default -Wswitch-enum -Wconversion \ --Wunreachable-code -Wformat=2 -Werror -Wuninitialized -Winit-self") - -if(DEBUG) - set(GCC_COMPILE_FLAGS ${GCC_COMPILE_FLAGS}" -g") -else() - set(GCC_COMPILE_FLAGS ${GCC_COMPILE_FLAGS}" -O2") -endif() - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_COVERAGE_COMPILE_FLAGS}") - -# add where to find the source files -# file(GLOB_RECURSE SOURCE ${PROJECT_SOURCE_DIR}/src/ *.cpp) -list(APPEND SOURCE ${PROJECT_SOURCE_DIR}/src/utils.cpp - ${PROJECT_SOURCE_DIR}/src/message.cpp - ${PROJECT_SOURCE_DIR}/src/signal.cpp - ${PROJECT_SOURCE_DIR}/src/dbc.cpp) - -set(HEADER_FILES - ${PROJECT_SOURCE_DIR}/include/libdbc/dbc.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/message.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/signal.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/utils/utils.hpp - ${PROJECT_SOURCE_DIR}/include/libdbc/exceptions/error.hpp -) - -include_directories(src) -include_directories(include) - -if(ENABLE_TESTS) - include(CTest) - add_subdirectory(test) -endif() - -add_subdirectory(doc) - -add_library(${PROJECT_NAME} STATIC ${SOURCE}) -target_link_libraries(${PROJECT_NAME} FastFloat::fast_float) - -if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) - target_sources(${PROJECT_NAME} INTERFACE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR}/include/libdbc - FILES ${HEADER_FILES}) -endif() - -add_custom_target(release - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - COMMAND ${CMAKE_SOURCE_DIR}/scripts/create_single_header.sh - DEPENDS ${PROJECT_NAME}) - -## Installation -# install lib -install(TARGETS ${PROJECT_NAME} - DESTINATION ${CMAKE_INSTALL_LIBDIR}) - -# install headers -if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) - install(TARGETS ${PROJECT_NAME} - FILE_SET HEADERS - DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/lib${PROJECT_NAME} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ) -else() - install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/libdbc DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) -endif() - -# Generate pkg-config file -configure_file(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY) -install( - FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc - DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +cmake_minimum_required(VERSION 3.16) + +# Keep this on one line for release checking +project(dbc VERSION 0.5.0 DESCRIPTION "C++ DBC Parser") + +# -- PROJECT OPTIONS -- # +option(DBC_ENABLE_TESTS "Enable Unittests" ON) +option(DBC_TEST_LOCALE_INDEPENDENCE "Used to deterime if the libary is locale agnostic when it comes to converting floats. You need `de_DE.UTF-8` locale installed for this testing." OFF) +option(DBC_GENERATE_DOCS "Use doxygen if installed to generated documentation files" OFF) +option(DBC_GENERATE_SINGLE_HEADER "This will run the generator for the single header file version. Default is OFF since we make a static build. Requires cargo installed." OFF) +# ---------------------- # + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# package +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_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE) +set(CPACK_RESOURCE_FILE_README ${CMAKE_CURRENT_SOURCE_DIR}/README.md) +include(CPack) + +# specify the C++ standard +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED True) + +find_package(FastFloat QUIET) +if (NOT ${FastFloat_FOUND}) + include(FetchContent) + FetchContent_Declare( + FastFloat + GIT_REPOSITORY https://github.com/fastfloat/fast_float.git + GIT_TAG 1ea4f27b2aeee2859a1354a3c24cff52a116cad1 + ) + FetchContent_MakeAvailable(FastFloat) +endif() + +# add where to find the source files +list(APPEND SOURCE_FILES + ${PROJECT_SOURCE_DIR}/src/utils.cpp + ${PROJECT_SOURCE_DIR}/src/message.cpp + ${PROJECT_SOURCE_DIR}/src/signal.cpp + ${PROJECT_SOURCE_DIR}/src/dbc.cpp +) + +list(APPEND HEADER_FILES + ${PROJECT_SOURCE_DIR}/include/libdbc/dbc.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/message.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/signal.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/utils/utils.hpp + ${PROJECT_SOURCE_DIR}/include/libdbc/exceptions/error.hpp +) + +if(DBC_ENABLE_TESTS) + include(CTest) + add_subdirectory(test) +endif() + +if(DBC_GENERATE_DOCS) + add_subdirectory(doc) +endif() + +list(APPEND GCC_CLANG_COMPILE_FLAGS + -Wall -Wextra -Wpedantic + -Wconversion -Wint-in-bool-context + -Wmissing-declarations -Wmissing-field-initializers + -Werror +) + + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + add_compile_options(/W4 /WX) +elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # Clang shadow warnings aren't as sensitive as gcc + add_compile_options(${GCC_CLANG_COMPILE_FLAGS} -Wshadow) +else() + add_compile_options(${GCC_CLANG_COMPILE_FLAGS}) +endif() + +add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES}) +target_link_libraries(${PROJECT_NAME} FastFloat::fast_float) +target_include_directories(${PROJECT_NAME} PUBLIC + $ + $ +) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_11) + +target_sources(${PROJECT_NAME} INTERFACE ${HEADER_FILES}) + +if(DBC_GENERATE_SINGLE_HEADER) + add_custom_target(single_header ALL + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMAND ${CMAKE_SOURCE_DIR}/scripts/create_single_header.sh + ) +endif() + +## Installation +# install lib +install(TARGETS ${PROJECT_NAME} + DESTINATION ${CMAKE_INSTALL_LIBDIR}) + +# install headers +install(DIRECTORY ${PROJECT_SOURCE_DIR}/include/libdbc DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + +# Generate pkg-config file +configure_file(${PROJECT_NAME}.pc.in ${PROJECT_NAME}.pc @ONLY) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.pc + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +# Clang tidy +add_custom_target(clang-tidy-check + clang-tidy -p ${CMAKE_BINARY_DIR}/compile_commands.json -warnings-as-errors=* ${SOURCE_FILES} ${HEADER_FILES} + DEPENDS ${SOURCE_FILES} ${HEADER_FILES} +) + +add_custom_target(clang-tidy-dump + clang-tidy -checks=-*,clang-analyzer-*,clang-analyzer-cplusplus*,cert-*,cppcoreguidelines-*,portability-*,readability-*,clang-diagnostic-* -dump-config -p ${CMAKE_BINARY_DIR}/compile_commands.json ${SOURCE_FILES} ${HEADER_FILES} > ../.clang-tidy + DEPENDS ${SOURCE_FILES} ${HEADER_FILES} +) + +add_custom_target(clang-tidy-fix + clang-tidy -fix-notes -p ${CMAKE_BINARY_DIR}/compile_commands.json ${SOURCE_FILES} ${HEADER_FILES} + DEPENDS ${SOURCE_FILES} ${HEADER_FILES} +) diff --git a/README.md b/README.md index 438ef36..8702453 100644 --- a/README.md +++ b/README.md @@ -4,32 +4,65 @@ This is to provide a library header only file to read in DBC files. I was lookin find a simple library that didn't have dependencies. So here we are making one. I got some inspiration from the python dbc library here: https://pypi.org/project/cantools/ -## Testing - -I am trying to always make sure that this is very well tested code. I am using Catch2 to do this -testing and if you aren't familiar here is the documentation: https://github.com/catchorg/Catch2/blob/master/docs/Readme.md#top - -To run the tests locally you can use the following: -```bash -mkdir build -cd build -cmake .. -make test -j -``` - ## Building I am using Cmake to be able to build the tests and the lib. I plan on doing more with it but this is what it -is for now. I am doing developement on the WSL Ubuntu 18.04 kernel. This doesn't mean that IDEs aren't +is for now. This doesn't mean that IDEs aren't welcome but the build process might not be suited for this. You will need to modify it for your needs. Feel free to submit changes so the building process will be more robust. Here are the steps to get started: ```bash -mkdir build +# Release Build +cmake -DCMAKE_BUILD_TYPE=Release -Bbuild -H. + +# Debug Build +cmake -DCMAKE_BUILD_TYPE=Debug -Bbuild -H. + +# Run the build +cmake --build build +``` + +### Listing Build Options + +You can check the latest build options with cmake. After you configure cmake you can run this. +```shell cd build -cmake .. -make + +# List this projects options +cmake -LH .. | grep -B1 "DBC_" + +# To see all the included project cache variables and options +cmake -LAH .. +``` + +### Creating a Single Header File + +It requires you have `cargo` installed from rust. See these instructions if you don't have that https://www.rust-lang.org/tools/install. +It uses the https://github.com/Felerius/cpp-amalgamate crate to do the single header file creation. + +The output will be generated in the `build/single_header/libdbc/` folder. You can run a cmake command to build this as well as other targets. + +To just build the single header you can simply run the target: +```shell +cmake -Bbuild -H. -DDBC_GENERATE_SINGLE_HEADER=ON + +cmake --build build --parallel `nproc` --target single_header +``` + + +## Testing + +I am trying to always make sure that this is very well tested code. I am using Catch2 to do this +testing and if you aren't familiar here is the documentation: https://github.com/catchorg/Catch2/blob/master/docs/Readme.md#top + +There is one option you will want for testing: `DBC_TEST_LOCALE_INDEPENDENCE`. This requires the `de_DE.UTF-8` locale installed to test. It is for checking we don't rely on locale to convert floats. i.e. 1.23 vs 1,23 + +You will need to configure the project to enable this: `cmake -DCMAKE_BUILD_TYPE=Release -DDBC_TEST_LOCALE_INDEPENDENCE=ON -Bbuild -H.`. You will get a warning if it isn't enabled because it isn't enabled by default. + +To run the tests locally you can use the following. Assuming you have built the project you should get a test executable. +```bash +ctest --output-on-failure --test-dir build ``` ## Scripts diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index b43d9e4..5e0e9e4 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -1,56 +1,62 @@ - -#ifndef __DBC_HPP__ -#define __DBC_HPP__ - -#include -#include -#include -#include - -#include - -namespace libdbc { - -class Parser { -public: - virtual ~Parser() = default; - - virtual void parse_file(const std::string& file) = 0; - -protected: -}; - -class DbcParser : public Parser { -public: - DbcParser(); - - virtual ~DbcParser() = default; - - virtual void parse_file(const std::string& file) final override; - - std::string get_version() const; - std::vector get_nodes() const; - std::vector get_messages() const; - - Message::ParseSignalsStatus parseMessage(const uint32_t id, const std::vector& data, std::vector& out_values); - -private: - std::string version; - std::vector nodes; - std::vector messages; - - const std::regex version_re; - const std::regex bit_timing_re; - const std::regex name_space_re; - const std::regex node_re; - const std::regex message_re; - const std::regex signal_re; - - void parse_dbc_header(std::istream& file_stream); - void parse_dbc_nodes(std::istream& file_stream); - void parse_dbc_messages(const std::vector& lines); -}; - -} - -#endif // __DBC_HPP__ +#ifndef DBC_HPP +#define DBC_HPP + +#include +#include +#include +#include +#include +#include + +namespace Libdbc { + +class Parser { +public: + virtual ~Parser() = default; + + virtual void parse_file(const std::string& file) = 0; + virtual void parse_file(std::istream& file) = 0; + +protected: +}; + +class DbcParser : public Parser { +public: + DbcParser(); + + void parse_file(const std::string& file_name) override; + void parse_file(std::istream& stream) override; + + std::string get_version() const; + std::vector get_nodes() const; + std::vector get_messages() const; + + Message::ParseSignalsStatus parse_message(uint32_t message_id, const std::vector& data, std::vector& out_values); + + std::vector unused_lines() const; + +private: + std::string version; + std::vector nodes; + std::vector messages; + + std::regex version_re; + std::regex bit_timing_re; + std::regex name_space_re; + std::regex node_re; + std::regex message_re; + std::regex value_re; + std::regex signal_re; + + std::vector missed_lines; + + void parse_dbc_header(std::istream& file_stream); + void parse_dbc_nodes(std::istream& file_stream); + void parse_dbc_messages(const std::vector& lines); + + static std::string get_extension(const std::string& file_name); +}; + +} + +#endif // DBC_HPP diff --git a/include/libdbc/exceptions/error.hpp b/include/libdbc/exceptions/error.hpp index 8fa3954..03fd5a0 100644 --- a/include/libdbc/exceptions/error.hpp +++ b/include/libdbc/exceptions/error.hpp @@ -1,24 +1,68 @@ -#ifndef __ERROR_HPP__ -#define __ERROR_HPP__ - -#include - -namespace libdbc { - -class exception : public std::exception { -public: - const char* what() const throw() { - return "libdbc exception occurred"; - } -}; - -class validity_error : public exception { -public: - const char* what() const throw() { - return "Invalid DBC file"; - } -}; - -} // libdbc - -#endif // __ERROR_HPP__ \ No newline at end of file +#ifndef ERROR_HPP +#define ERROR_HPP + +#include +#include + +namespace Libdbc { + +class Exception : public std::exception { +public: + const char* what() const throw() override { + return "libdbc exception occurred"; + } +}; + +class ValidityError : public Exception { +public: + const char* what() const throw() override { + return "Invalid DBC file"; + } +}; + +class NonDbcFileFormatError : public ValidityError { +public: + NonDbcFileFormatError(const std::string& path, const std::string& extension) { + error_msg = {"File is not of DBC format. Expected a .dbc extension. Cannot read this type of file (" + path + "). Found the extension (" + extension + + ")."}; + } + + const char* what() const throw() override { + return error_msg.c_str(); + } + +private: + std::string error_msg; +}; + +class DbcFileIsMissingVersion : public ValidityError { +public: + DbcFileIsMissingVersion(const std::string& line) { + error_msg = {"Invalid dbc file. Missing the required version header. Attempting to read line: (" + line + ")."}; + } + + const char* what() const throw() override { + return error_msg.c_str(); + } + +private: + std::string error_msg; +}; + +class DbcFileIsMissingBitTiming : public ValidityError { +public: + DbcFileIsMissingBitTiming(const std::string& line) { + error_msg = {"Invalid dbc file. Missing required bit timing in the header. Attempting to read line: (" + line + ")."}; + } + + const char* what() const throw() override { + return error_msg.c_str(); + } + +private: + std::string error_msg; +}; + +} // libdbc + +#endif // ERROR_HPP diff --git a/include/libdbc/message.hpp b/include/libdbc/message.hpp index 90a1dc1..438dc7f 100644 --- a/include/libdbc/message.hpp +++ b/include/libdbc/message.hpp @@ -1,51 +1,49 @@ -#ifndef __MESSAGE_HPP__ -#define __MESSAGE_HPP__ - -#include -#include -#include -#include -#include - -namespace libdbc { -struct Message { - Message() = delete; - explicit Message(uint32_t id, const std::string& name, uint8_t size, const std::string& node); - - enum class ParseSignalsStatus { - Success, - ErrorMessageToLong, - ErrorBigEndian, - ErrorUnknownID, - ErrorInvalidConversion, - }; - - /*! - * \brief parseSignals - * \param data - * \param values - * \return - */ - ParseSignalsStatus parseSignals(const std::vector& data, std::vector& values) const; - - void appendSignal(const Signal& signal); - const std::vector signals() const; - uint32_t id() const; - - virtual bool operator==(const Message& rhs) const; - -private: - uint32_t m_id; - std::string m_name; - uint8_t m_size; - std::string m_node; - std::vector m_signals; - - friend std::ostream& operator<<(std::ostream& os, const Message& dt); -}; - -std::ostream& operator<<(std::ostream& out, const Message& msg); - -} - -#endif // __MESSAGE_HPP__ +#ifndef MESSAGE_HPP +#define MESSAGE_HPP + +#include +#include +#include +#include +#include + +namespace Libdbc { +struct Message { + Message() = delete; + virtual ~Message() = default; + explicit Message(uint32_t message_id, const std::string& name, uint8_t size, const std::string& node); + + enum class ParseSignalsStatus { + Success, + ErrorMessageToLong, + ErrorBigEndian, + ErrorUnknownID, + ErrorInvalidConversion, + }; + + ParseSignalsStatus parse_signals(const std::vector& data, std::vector& values) const; + + void append_signal(const Signal& signal); + std::vector get_signals() const; + uint32_t id() const; + uint8_t size() const; + const std::string& name() const; + void add_value_description(const std::string& signal_name, const std::vector&); + + virtual bool operator==(const Message& rhs) const; + +private: + uint32_t m_id; + std::string m_name; + uint8_t m_size; + std::string m_node; + std::vector m_signals; + + friend std::ostream& operator<<(std::ostream& out, const Message& msg); +}; + +std::ostream& operator<<(std::ostream& out, const Message& msg); + +} + +#endif // MESSAGE_HPP diff --git a/include/libdbc/signal.hpp b/include/libdbc/signal.hpp index 30178de..18ed3da 100644 --- a/include/libdbc/signal.hpp +++ b/include/libdbc/signal.hpp @@ -1,46 +1,54 @@ - -#ifndef __SIGNAL_HPP__ -#define __SIGNAL_HPP__ - -#include -#include -#include - -namespace libdbc { -struct Signal { - std::string name; - bool is_multiplexed; - uint32_t start_bit; - uint32_t size; - bool is_bigendian; - bool is_signed; - double factor; - double offset; - double min; - double max; - std::string unit; - std::vector receivers; - - Signal() = delete; - explicit Signal(std::string name, - bool is_multiplexed, - uint32_t start_bit, - uint32_t size, - bool is_bigendian, - bool is_signed, - double factor, - double offset, - double min, - double max, - std::string unit, - std::vector recievers); - - virtual bool operator==(const Signal& rhs) const; - bool operator<(const Signal& rhs) const; -}; - -std::ostream& operator<<(std::ostream& out, const Signal& sig); - -} - -#endif // __SIGNAL_HPP__ + +#ifndef SIGNAL_HPP +#define SIGNAL_HPP + +#include +#include +#include +#include + +namespace Libdbc { +struct Signal { + struct ValueDescription { + uint32_t value; + std::string description; + }; + + std::string name; + bool is_multiplexed; + uint32_t start_bit; + uint32_t size; + bool is_bigendian; + bool is_signed; + double factor; + double offset; + double min; + double max; + std::string unit; + std::vector receivers; + std::vector value_descriptions; + + Signal() = delete; + virtual ~Signal() = default; + explicit Signal(std::string name, + bool is_multiplexed, + uint32_t start_bit, + uint32_t size, + bool is_bigendian, + bool is_signed, + double factor, + double offset, + double min, + double max, + std::string unit, + std::vector receivers); + + virtual bool operator==(const Signal& rhs) const; + bool operator<(const Signal& rhs) const; +}; + +std::ostream& operator<<(std::ostream& out, const Signal& sig); + +} + +#endif // SIGNAL_HPP diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index a7dde52..fd2fba2 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -1,49 +1,48 @@ - -#ifndef __UTILS_HPP__ -#define __UTILS_HPP__ - -#include -#include -#include -#include -#include -#include - -namespace utils { - -class StreamHandler { -public: - /** - * This is a safe non line ending specific get_ine function. This is to help with files - * carried over from different systems. i.e Unix file comes to Windows with LF endings - * instead of CRLF. - * - * @param stream [description] - * @param line [description] - * @return [description] - */ - static std::istream& get_line(std::istream& stream, std::string& line); - - static std::istream& get_next_non_blank_line(std::istream& stream, std::string& line); - - static std::istream& skip_to_next_blank_line(std::istream& stream, std::string& line); -}; - -class String { -public: - static std::string trim(const std::string& line); - - template - static void split(const std::string& str, Container& cont, char delim = ' ') { - std::stringstream ss(str); - std::string token; - - while (std::getline(ss, token, delim)) { - cont.push_back(token); - } - } -}; - -} - -#endif // __UTILS_HPP__ + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include +#include + +namespace Utils { + +class StreamHandler { +public: + /** + * This is a safe non line ending specific get_ine function. This is to help with files + * carried over from different systems. i.e Unix file comes to Windows with LF endings + * instead of CRLF. + * + * @param stream [description] + * @param line [description] + * @return [description] + */ + static std::istream& get_line(std::istream& stream, std::string& line); + + static std::istream& get_next_non_blank_line(std::istream& stream, std::string& line); + + static std::istream& skip_to_next_blank_line(std::istream& stream, std::string& line); +}; + +class String { +public: + static std::string trim(const std::string& line); + + template + static void split(const std::string& str, Container& cont, char delim = ' ') { + std::stringstream stream(str); + std::string token; + + while (std::getline(stream, token, delim)) { + cont.push_back(token); + } + } + + static double convert_to_double(const std::string& value, double default_value = 0); +}; + +} + +#endif // UTILS_HPP diff --git a/script-requirements.txt b/script-requirements.txt deleted file mode 100644 index 86aed3c..0000000 --- a/script-requirements.txt +++ /dev/null @@ -1 +0,0 @@ -Quom==1.2.0 \ No newline at end of file diff --git a/scripts/check_version.py b/scripts/check_version.py new file mode 100755 index 0000000..3881db4 --- /dev/null +++ b/scripts/check_version.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# Used to check the input verion against the input +import re +import argparse + + +def get_cmake_version(cmake_file): + with open(cmake_file, 'r') as f: + contents = f.read() + match = re.search(r'project\(.*VERSION (\d+)\.(\d+)\.(\d+)', contents) + if match: + major, minor, patch = map(int, match.groups()) + return major, minor, patch + return None + + +def validate_semver(version): + pattern = r'^v(\d+)\.(\d+)\.(\d+)$' + match = re.match(pattern, version) + if match: + return tuple(map(int, match.groups())) + else: + return None + + +def compare_versions(input_version, cmake_version): + if input_version > cmake_version: + print("Input version is greater than CMake version.") + exit(1) + elif input_version < cmake_version: + print("Input version is smaller than CMake version.") + exit(1) + else: + print("Input version is equal to CMake version.") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Check input version against CMake project version") + parser.add_argument("--version", type=str, help="Input version with a 'v' prefix", required=True) + args = parser.parse_args() + + cmake_version = get_cmake_version("CMakeLists.txt") + if cmake_version is None: + print("Failed to retrieve version from CMakeLists.txt.") + exit(1) + else: + input_version = validate_semver(args.version) + if input_version is None: + print("Invalid input version format. Please provide a version in the format 'vX.Y.Z'") + exit(1) + else: + compare_versions(input_version, cmake_version) + + + diff --git a/scripts/create_single_header.sh b/scripts/create_single_header.sh old mode 100644 new mode 100755 index 71ee4fa..a740133 --- a/scripts/create_single_header.sh +++ b/scripts/create_single_header.sh @@ -1 +1,13 @@ -quom -I include -I src src/dbc.cpp build/libdbc.hpp \ No newline at end of file +#!/bin/sh + +set -e + +cargo install cpp-amalgamate + +rm -rf build/single_header/ +mkdir -p build/single_header/libdbc + +source_files=$(find src -name "*.cpp") +include_files=$(find include -name "*.hpp") + +cpp-amalgamate -d include -d build/_deps/fastfloat-src/include -o build/single_header/libdbc/libdbc.hpp ${source_files} ${include_files} diff --git a/scripts/fmt.sh b/scripts/fmt.sh index 8f360f8..9e15b4e 100755 --- a/scripts/fmt.sh +++ b/scripts/fmt.sh @@ -11,8 +11,8 @@ FMT="" # Some distros just call it clang-format. Others (e.g. Ubuntu) are insistent # that the version number be part of the command. We prefer clang-format if -# that's present, otherwise we check clang-format-13 -for clangfmt in clang-format{,-13}; do +# that's present, otherwise we check clang-format-16 +for clangfmt in clang-format{,-16}; do if which "$clangfmt" &>/dev/null; then FMT="$clangfmt" break @@ -21,7 +21,7 @@ done # Check if we found a working clang-format if [ -z "$FMT" ]; then - echo "failed to find clang-format. Please install clang-format version 13 or above" + echo "failed to find clang-format. Please install clang-format version 16 or above" exit 1 fi diff --git a/src/dbc.cpp b/src/dbc.cpp index c091408..89e8c24 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -1,165 +1,253 @@ -#include -#include -#include -#include - -#include - -namespace { - -const auto floatPattern = "(-?\\d+\\.?(\\d+)?)"; // Can be negative - -const auto signalIdentifierPattern = "(SG_)"; -const auto namePattern = "(\\w+)"; -const auto bitStartPattern = "(\\d+)"; // Cannot be negative -const auto lengthPattern = "(\\d+)"; // Cannot be negative -const auto byteOrderPattern = "([0-1])"; -const auto signPattern = "(\\+|\\-)"; -const auto scalePattern = "(\\d+\\.?(\\d+)?)"; // Non negative float -const auto offsetPattern = floatPattern; -const auto offsetScalePattern = std::string("\\(") + scalePattern + "\\," + offsetPattern + "\\)"; -const auto minPattern = floatPattern; -const auto maxPattern = floatPattern; -const auto minMaxPattern = std::string("\\[") + minPattern + "\\|" + maxPattern + "\\]"; -const auto unitPattern = "\"(.*)\""; // Random string -const auto receiverPattern = "([\\w\\,]+|Vector__XXX)*"; -const auto whiteSpace = "\\s"; - -} // anonymous namespace - -namespace libdbc { - -DbcParser::DbcParser() - : version("") - , nodes() - , version_re("^(VERSION)\\s\"(.*)\"") - , bit_timing_re("^(BS_:)") - , name_space_re("^(NS_)\\s\\:") - , node_re("^(BU_:)\\s((?:[\\w]+?\\s?)*)") - , message_re("^(BO_)\\s(\\d+)\\s(\\w+)\\:\\s(\\d+)\\s(\\w+|Vector__XXX)") - , - // NOTE: No multiplex support yet - signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + whiteSpace + "\\:" + whiteSpace + bitStartPattern + "\\|" - + lengthPattern + "\\@" + byteOrderPattern + signPattern + whiteSpace + offsetScalePattern + whiteSpace + minMaxPattern + whiteSpace + unitPattern - + whiteSpace + receiverPattern) { -} - -void DbcParser::parse_file(const std::string& file) { - std::ifstream s(file.c_str()); - std::string line; - std::vector lines; - - messages.clear(); - - parse_dbc_header(s); - - parse_dbc_nodes(s); - - while (!s.eof()) { - utils::StreamHandler::get_next_non_blank_line(s, line); - lines.push_back(line); - } - - parse_dbc_messages(lines); -} - -std::string DbcParser::get_version() const { - return version; -} - -std::vector DbcParser::get_nodes() const { - return nodes; -} - -std::vector DbcParser::get_messages() const { - return messages; -} - -Message::ParseSignalsStatus DbcParser::parseMessage(const uint32_t id, const std::vector& data, std::vector& out_values) { - for (const auto& message : messages) { - if (message.id() == id) - return message.parseSignals(data, out_values); - } - return Message::ParseSignalsStatus::ErrorUnknownID; -} - -void DbcParser::parse_dbc_header(std::istream& file_stream) { - std::string line; - std::smatch match; - - utils::StreamHandler::get_line(file_stream, line); - - if (!std::regex_search(line, match, version_re)) { - throw validity_error(); - } - - version = match.str(2); - - utils::StreamHandler::get_next_non_blank_line(file_stream, line); - - utils::StreamHandler::skip_to_next_blank_line(file_stream, line); - - utils::StreamHandler::get_next_non_blank_line(file_stream, line); - - if (!std::regex_search(line, match, bit_timing_re)) - throw validity_error(); -} - -void DbcParser::parse_dbc_nodes(std::istream& file_stream) { - std::string line; - std::smatch match; - - utils::StreamHandler::get_next_non_blank_line(file_stream, line); - - if (!std::regex_search(line, match, node_re)) - throw validity_error(); - - if (match.length() > 2) { - std::string n = match.str(2); - utils::String::split(n, nodes); - } -} - -void DbcParser::parse_dbc_messages(const std::vector& lines) { - std::smatch match; - - for (const auto& line : lines) { - if (std::regex_search(line, match, message_re)) { - uint32_t id = std::stoul(match.str(2)); - std::string name = match.str(3); - uint8_t size = std::stoul(match.str(4)); - std::string node = match.str(5); - - Message msg(id, name, size, node); - - messages.push_back(msg); - } - - if (std::regex_search(line, match, signal_re)) { - std::string name = match.str(2); - bool is_multiplexed = false; // No support yet - uint32_t start_bit = std::stoul(match.str(3)); - uint32_t size = std::stoul(match.str(4)); - bool is_bigendian = (std::stoul(match.str(5)) == 0); - bool is_signed = (match.str(6) == "-"); - // Alternate groups because a group is for the decimal portion - double factor; - fast_float::from_chars(match.str(7).data(), match.str(7).data() + match.str(7).size(), factor); - double offset; - fast_float::from_chars(match.str(9).data(), match.str(9).data() + match.str(9).size(), offset); - double min; - fast_float::from_chars(match.str(11).data(), match.str(11).data() + match.str(11).size(), min); - double max; - fast_float::from_chars(match.str(13).data(), match.str(13).data() + match.str(13).size(), max); - std::string unit = match.str(15); - - std::vector receivers; - utils::String::split(match.str(16), receivers, ','); - - Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers); - messages.back().appendSignal(sig); - } - } -} - -} +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Libdbc { + +const auto floatPattern = "(-?\\d+\\.?(\\d+)?)"; // Can be negative + +const auto signalIdentifierPattern = "(SG_)"; +const auto namePattern = "(\\w+)"; +const auto bitStartPattern = "(\\d+)"; // Cannot be negative +const auto lengthPattern = "(\\d+)"; // Cannot be negative +const auto byteOrderPattern = "([0-1])"; +const auto signPattern = "(\\+|\\-)"; +const auto scalePattern = "(\\d+\\.?(\\d+)?)"; // Non negative float +const auto offsetPattern = floatPattern; +// NOLINTNEXTLINE -- Disable warning for runtime initialization and can throw. Can't fix until newer c++ version with constexpr +const auto offsetScalePattern = std::string("\\(") + scalePattern + "\\," + offsetPattern + "\\)"; +const auto minPattern = floatPattern; +const auto maxPattern = floatPattern; +// NOLINTNEXTLINE -- Disable warning for runtime initialization and can throw. Can't fix until newer c++ version with constexpr +const auto minMaxPattern = std::string("\\[") + minPattern + "\\|" + maxPattern + "\\]"; +const auto unitPattern = "\"(.*)\""; // Random string +const auto receiverPattern = "([\\w\\,]+|Vector__XXX)*"; +const auto whiteSpace = "\\s"; + +constexpr unsigned SIGNAL_NAME_GROUP = 2; +constexpr unsigned SIGNAL_START_BIT_GROUP = 3; +constexpr unsigned SIGNAL_SIZE_GROUP = 4; +constexpr unsigned SIGNAL_ENDIAN_GROUP = 5; +constexpr unsigned SIGNAL_SIGNED_GROUP = 6; +constexpr unsigned SIGNAL_FACTOR_GROUP = 7; +constexpr unsigned SIGNAL_OFFSET_GROUP = 9; +constexpr unsigned SIGNAL_MIN_GROUP = 11; +constexpr unsigned SIGNAL_MAX_GROUP = 13; +constexpr unsigned SIGNAL_UNIT_GROUP = 15; +constexpr unsigned SIGNAL_RECIEVER_GROUP = 16; + +constexpr unsigned MESSAGE_ID_GROUP = 2; +constexpr unsigned MESSAGE_NAME_GROUP = 3; +constexpr unsigned MESSAGE_SIZE_GROUP = 4; +constexpr unsigned MESSAGE_NODE_GROUP = 5; + +struct Value { + uint32_t can_id; + std::string signal_name; + std::vector value_descriptions; +}; + +DbcParser::DbcParser() + : version_re("^(VERSION)\\s\"(.*)\"") + , bit_timing_re("^(BS_:)") + , name_space_re("^(NS_)\\s\\:") + , node_re("^(BU_:)\\s?((?:[\\w]+?\\s?)*)?") + , message_re("^(BO_)\\s(\\d+)\\s(\\w+)\\:\\s(\\d+)\\s(\\w+|Vector__XXX)") + , value_re("^(VAL_)\\s(\\d+)\\s(\\w+)((?:\\s(\\d+)\\s\"([^\"]*)\")+)\\s;$") + , + // NOTE: No multiplex support yet + signal_re(std::string("^") + whiteSpace + signalIdentifierPattern + whiteSpace + namePattern + whiteSpace + "\\:" + whiteSpace + bitStartPattern + "\\|" + + lengthPattern + "\\@" + byteOrderPattern + signPattern + whiteSpace + offsetScalePattern + whiteSpace + minMaxPattern + whiteSpace + unitPattern + + whiteSpace + receiverPattern) { +} + +void DbcParser::parse_file(std::istream& stream) { + std::string line; + std::vector lines; + + messages.clear(); + + parse_dbc_header(stream); + parse_dbc_nodes(stream); + + while (!stream.eof()) { + Utils::StreamHandler::get_next_non_blank_line(stream, line); + lines.push_back(line); + } + + parse_dbc_messages(lines); +} + +void DbcParser::parse_file(const std::string& file_name) { + auto extension = get_extension(file_name); + if (extension != ".dbc") { + throw NonDbcFileFormatError(file_name, extension); + } + + std::ifstream stream(file_name.c_str()); + + parse_file(stream); +} + +std::string DbcParser::get_extension(const std::string& file_name) { + std::size_t dot = file_name.find_last_of("."); + if (dot != std::string::npos) { + return file_name.substr(dot, file_name.size() - dot); + } + + return ""; +} + +std::string DbcParser::get_version() const { + return version; +} + +std::vector DbcParser::get_nodes() const { + return nodes; +} + +std::vector DbcParser::get_messages() const { + return messages; +} + +Message::ParseSignalsStatus DbcParser::parse_message(const uint32_t message_id, const std::vector& data, std::vector& out_values) { + for (const auto& message : messages) { + if (message.id() == message_id) { + return message.parse_signals(data, out_values); + } + } + return Message::ParseSignalsStatus::ErrorUnknownID; +} + +void DbcParser::parse_dbc_header(std::istream& file_stream) { + std::string line; + std::smatch match; + + Utils::StreamHandler::get_line(file_stream, line); + + if (!std::regex_search(line, match, version_re)) { + throw DbcFileIsMissingVersion(line); + } + + version = match.str(2); + + Utils::StreamHandler::get_next_non_blank_line(file_stream, line); + Utils::StreamHandler::skip_to_next_blank_line(file_stream, line); + Utils::StreamHandler::get_next_non_blank_line(file_stream, line); + + if (!std::regex_search(line, match, bit_timing_re)) { + throw DbcFileIsMissingBitTiming(line); + } +} + +void DbcParser::parse_dbc_nodes(std::istream& file_stream) { + std::string line; + std::smatch match; + + Utils::StreamHandler::get_next_non_blank_line(file_stream, line); + + std::regex_search(line, match, node_re); + + if (match.length() > 2) { + std::string node = match.str(2); + Utils::String::split(node, nodes); + } +} + +void DbcParser::parse_dbc_messages(const std::vector& lines) { + std::smatch match; + + std::vector signal_value; + + for (const auto& line : lines) { + if (std::regex_search(line, match, message_re)) { + uint32_t message_id = static_cast(std::stoul(match.str(MESSAGE_ID_GROUP))); + std::string name = match.str(MESSAGE_NAME_GROUP); + uint8_t size = static_cast(std::stoul(match.str(MESSAGE_SIZE_GROUP))); + std::string node = match.str(MESSAGE_NODE_GROUP); + + Message msg(message_id, name, size, node); + + messages.push_back(msg); + continue; + } + + if (std::regex_search(line, match, signal_re) && !messages.empty()) { + std::string name = match.str(SIGNAL_NAME_GROUP); + bool is_multiplexed = false; // No support yet + uint32_t start_bit = static_cast(std::stoul(match.str(SIGNAL_START_BIT_GROUP))); + uint32_t size = static_cast(std::stoul(match.str(SIGNAL_SIZE_GROUP))); + bool is_bigendian = (std::stoul(match.str(SIGNAL_ENDIAN_GROUP)) == 0); + bool is_signed = (match.str(SIGNAL_SIGNED_GROUP) == "-"); + + double factor = Utils::String::convert_to_double(match.str(SIGNAL_FACTOR_GROUP)); + double offset = Utils::String::convert_to_double(match.str(SIGNAL_OFFSET_GROUP)); + double min = Utils::String::convert_to_double(match.str(SIGNAL_MIN_GROUP)); + double max = Utils::String::convert_to_double(match.str(SIGNAL_MAX_GROUP)); + + std::string unit = match.str(SIGNAL_UNIT_GROUP); + + std::vector receivers; + Utils::String::split(match.str(SIGNAL_RECIEVER_GROUP), receivers, ','); + + Signal sig(name, is_multiplexed, start_bit, size, is_bigendian, is_signed, factor, offset, min, max, unit, receivers); + messages.back().append_signal(sig); + continue; + } + + if (std::regex_search(line, match, value_re) && !messages.empty()) { + uint32_t message_id = static_cast(std::stoul(match.str(2))); + std::string signal_name = match.str(3); + + // Loop over the rest of the descriptions + std::string rest_of_descriptions = match.str(4); + std::regex description_re("\\s(\\d+)\\s\"([^\"]*)\""); + + std::sregex_iterator desc_iter(rest_of_descriptions.begin(), rest_of_descriptions.end(), description_re); + std::sregex_iterator desc_end = std::sregex_iterator(); + + std::vector values{}; + for (std::sregex_iterator i = desc_iter; i != desc_end; ++i) { + std::smatch desc_match = *desc_iter; + uint32_t number = static_cast(std::stoul(desc_match.str(1))); + std::string text = desc_match.str(2); + + values.push_back(Signal::ValueDescription{number, text}); + ++desc_iter; + } + + Value val{message_id, signal_name, values}; + + signal_value.push_back(val); + continue; + } + + if (line.length() > 0) { + missed_lines.push_back(line); + } + } + + for (const auto& signal : signal_value) { + for (auto& msg : messages) { + if (msg.id() == signal.can_id) { + msg.add_value_description(signal.signal_name, signal.value_descriptions); + break; + } + } + } +} + +std::vector DbcParser::unused_lines() const { + return missed_lines; +} + +} diff --git a/src/message.cpp b/src/message.cpp index 7a2331b..ffe4526 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,85 +1,127 @@ -#include -#include - -namespace libdbc { -Message::Message(uint32_t id, const std::string& name, uint8_t size, const std::string& node) - : m_id(id) - , m_name(name) - , m_size(size) - , m_node(node) { -} - -bool Message::operator==(const Message& rhs) const { - return (m_id == rhs.id()) && (m_name == rhs.m_name) && (m_size == rhs.m_size) && (m_node == rhs.m_node); -} - -Message::ParseSignalsStatus Message::parseSignals(const std::vector& data, std::vector& values) const { - int size = data.size(); - if (size > 8) - return ParseSignalsStatus::ErrorMessageToLong; // not supported yet - - uint64_t data_little_endian = 0; - uint64_t data_big_endian = 0; - for (int i = 0; i < size; i++) { - data_little_endian |= ((uint64_t)data[i]) << i * 8; - data_big_endian = (data_big_endian << 8) | (uint64_t)data[i]; - } - - // TODO: does this also work on a big endian machine? - - const uint32_t len = size * 8; - uint64_t v = 0; - for (const auto& signal : m_signals) { - if (signal.is_bigendian) { - uint32_t start_bit = 8 * (signal.start_bit / 8) + (7 - (signal.start_bit % 8)); // Calculation taken from python CAN - v = data_big_endian << start_bit; - v = v >> (len - signal.size); - } else { - const uint32_t shiftLeft = (len - (signal.size + signal.start_bit)); - v = data_little_endian << shiftLeft; - v = v >> (shiftLeft + signal.start_bit); - } - - if (signal.is_signed && signal.size > 1) { - switch (signal.size) { - case 8: - values.push_back((int8_t)v * signal.factor + signal.offset); - break; - case 16: - values.push_back((int16_t)v * signal.factor + signal.offset); - break; - case 32: - values.push_back((int32_t)v * signal.factor + signal.offset); - break; - case 64: - values.push_back((int64_t)v * signal.factor + signal.offset); - break; - default: - return ParseSignalsStatus::ErrorInvalidConversion; - } - } else - values.push_back(v * signal.factor + signal.offset); - } - return ParseSignalsStatus::Success; -} - -void Message::appendSignal(const Signal& signal) { - m_signals.push_back(signal); -} - -const std::vector Message::signals() const { - return m_signals; -} - -uint32_t Message::id() const { - return m_id; -} - -std::ostream& operator<<(std::ostream& out, const Message& msg) { - out << "Message: {id: " << msg.id() << ", "; - out << "name: " << msg.m_name << ", "; - out << "size: " << msg.m_size << ", "; - out << "node: " << msg.m_node << "}"; - return out; -} -} +#include +#include +#include +#include +#include +#include +#include + +namespace Libdbc { + +constexpr unsigned ONE_BYTE = 8; +constexpr unsigned TWO_BYTES = 16; +constexpr unsigned FOUR_BYTES = 32; +constexpr unsigned EIGHT_BYTES = 64; + +constexpr unsigned SEVEN_BITS = 7; + +Message::Message(uint32_t message_id, const std::string& name, uint8_t size, const std::string& node) + : m_id(message_id) + , m_name(name) + , m_size(size) + , m_node(node) { +} + +bool Message::operator==(const Message& rhs) const { + return (m_id == rhs.id()) && (m_name == rhs.m_name) && (m_size == rhs.m_size) && (m_node == rhs.m_node); +} + +Message::ParseSignalsStatus Message::parse_signals(const std::vector& data, std::vector& values) const { + auto size = data.size(); + if (size > ONE_BYTE) { + return ParseSignalsStatus::ErrorMessageToLong; // not supported yet + } + + uint64_t data_little_endian = 0; + uint64_t data_big_endian = 0; + for (std::size_t i = 0; i < size; i++) { + data_little_endian |= ((uint64_t)data[i]) << i * ONE_BYTE; + data_big_endian = (data_big_endian << ONE_BYTE) | (uint64_t)data[i]; + } + + // TODO: does this also work on a big endian machine? + + const auto len = size * 8; + uint64_t value = 0; + for (const auto& signal : m_signals) { + if (signal.is_bigendian) { + uint32_t start_bit = ONE_BYTE * (signal.start_bit / ONE_BYTE) + (SEVEN_BITS - (signal.start_bit % ONE_BYTE)); // Calculation taken from python CAN + value = data_big_endian << start_bit; + value = value >> (len - signal.size); + } else { + value = data_little_endian >> signal.start_bit; + } + + if (signal.is_signed && signal.size > 1) { + switch (signal.size) { + case ONE_BYTE: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + case TWO_BYTES: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + case FOUR_BYTES: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + case EIGHT_BYTES: + values.push_back(static_cast(value) * signal.factor + signal.offset); + break; + default: { + // 2 complement -> decimal + const bool is_negative = (value & (1ULL << (signal.size - 1))) != 0; + int64_t nativeInt = 0; + if (is_negative) { + nativeInt = static_cast(value | ~((1ULL << signal.size) - 1)); // invert all bits above signal.size + } else { + nativeInt = static_cast(value & ((1ULL << signal.size) - 1)); // masking + } + values.push_back(static_cast(nativeInt) * signal.factor + signal.offset); + break; + } + } + } else { + // use only the relevant bits + value = value & ((1 << signal.size) - 1); // masking + values.push_back(static_cast(value) * signal.factor + signal.offset); + } + } + return ParseSignalsStatus::Success; +} + +void Message::append_signal(const Signal& signal) { + m_signals.push_back(signal); +} + +std::vector Message::get_signals() const { + return m_signals; +} + +uint32_t Message::id() const { + return m_id; +} + +uint8_t Message::size() const { + return m_size; +} + +const std::string& Message::name() const { + return m_name; +} + +void Message::add_value_description(const std::string& signal_name, const std::vector& value_descriptor) { + for (auto& signal : m_signals) { + if (signal.name == signal_name) { + signal.value_descriptions = value_descriptor; + return; + } + } +} + +std::ostream& operator<<(std::ostream& out, const Message& msg) { + out << "Message: {id: " << msg.id() << ", "; + out << "name: " << msg.m_name << ", "; + out << "size: " << msg.m_size << ", "; + out << "node: " << msg.m_node << "}"; + return out; +} +} diff --git a/src/signal.cpp b/src/signal.cpp index c4b703b..d40bf2f 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -1,54 +1,59 @@ -#include - -namespace libdbc { -Signal::Signal(std::string name, - bool is_multiplexed, - uint32_t start_bit, - uint32_t size, - bool is_bigendian, - bool is_signed, - double factor, - double offset, - double min, - double max, - std::string unit, - std::vector receivers) - : name(name) - , is_multiplexed(is_multiplexed) - , start_bit(start_bit) - , size(size) - , is_bigendian(is_bigendian) - , is_signed(is_signed) - , factor(factor) - , offset(offset) - , min(min) - , max(max) - , unit(unit) - , receivers(receivers) { -} - -bool Signal::operator==(const Signal& rhs) const { - return (this->name == rhs.name) && (this->is_multiplexed == rhs.is_multiplexed) && (this->start_bit == rhs.start_bit) && (this->size == rhs.size) - && (this->is_bigendian == rhs.is_bigendian) && (this->is_signed == rhs.is_signed) && (this->offset == rhs.offset) && (this->min == rhs.min) - && (this->max == rhs.max) && (this->unit == rhs.unit) && (this->receivers == rhs.receivers); -} - -bool Signal::operator<(const Signal& rhs) const { - return start_bit < rhs.start_bit; -} - -std::ostream& operator<<(std::ostream& out, const Signal& sig) { - out << "Signal {name: " << sig.name << ", "; - out << "Multiplexed: " << (sig.is_multiplexed ? "True" : "False") << ", "; - out << "Start bit: " << sig.start_bit << ", "; - out << "Size: " << sig.size << ", "; - out << "Endianness: " << (sig.is_bigendian ? "Big endian" : "Little endian") << ", "; - out << "Value Type: " << (sig.is_signed ? "Signed" : "Unsigned") << ", "; - out << "Min: " << sig.min << ", Max: " << sig.max << ", "; - out << "Unit: (" << sig.unit << "), "; - out << "receivers: "; - for (const auto& r : sig.receivers) - out << r; - return out << "}"; -} -} \ No newline at end of file +#include +#include +#include +#include +#include + +namespace Libdbc { +Signal::Signal(std::string name, + bool is_multiplexed, + uint32_t start_bit, + uint32_t size, + bool is_bigendian, + bool is_signed, + double factor, + double offset, + double min, + double max, + std::string unit, + std::vector receivers) + : name(name) + , is_multiplexed(is_multiplexed) + , start_bit(start_bit) + , size(size) + , is_bigendian(is_bigendian) + , is_signed(is_signed) + , factor(factor) + , offset(offset) + , min(min) + , max(max) + , unit(unit) + , receivers(receivers) { +} + +bool Signal::operator==(const Signal& rhs) const { + return (this->name == rhs.name) && (this->is_multiplexed == rhs.is_multiplexed) && (this->start_bit == rhs.start_bit) && (this->size == rhs.size) + && (this->is_bigendian == rhs.is_bigendian) && (this->is_signed == rhs.is_signed) && (this->offset == rhs.offset) && (this->min == rhs.min) + && (this->max == rhs.max) && (this->unit == rhs.unit) && (this->receivers == rhs.receivers); +} + +bool Signal::operator<(const Signal& rhs) const { + return start_bit < rhs.start_bit; +} + +std::ostream& operator<<(std::ostream& out, const Signal& sig) { + out << "Signal {name: " << sig.name << ", "; + out << "Multiplexed: " << (sig.is_multiplexed ? "True" : "False") << ", "; + out << "Start bit: " << sig.start_bit << ", "; + out << "Size: " << sig.size << ", "; + out << "Endianness: " << (sig.is_bigendian ? "Big endian" : "Little endian") << ", "; + out << "Value Type: " << (sig.is_signed ? "Signed" : "Unsigned") << ", "; + out << "Min: " << sig.min << ", Max: " << sig.max << ", "; + out << "Unit: (" << sig.unit << "), "; + out << "receivers: "; + for (const auto& reciever : sig.receivers) { + out << reciever; + } + return out << "}"; +} +} diff --git a/src/utils.cpp b/src/utils.cpp index bd6e4fd..eed3134 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -1,72 +1,82 @@ -#include - -#include - -namespace utils { - -std::istream& StreamHandler::get_line(std::istream& stream, std::string& line) { - std::string newline; - - std::getline(stream, newline); - - // Windows CRLF (\r\n) - if (newline.size() && newline[newline.size() - 1] == '\r') { - line = newline.substr(0, newline.size() - 1); - // MacOS LF (\r) - } else if (newline.size() && newline[newline.size()] == '\r') { - line = newline.replace(newline.size(), 1, "\n"); - } else { - line = newline; - } - - return stream; -} - -std::istream& StreamHandler::get_next_non_blank_line(std::istream& stream, std::string& line) { - bool is_blank = true; - - const std::regex whitespace_re("\\s*(.*)"); - std::smatch match; - - while (is_blank) { - utils::StreamHandler::get_line(stream, line); - - std::regex_search(line, match, whitespace_re); - - if ((!line.empty() && !match.empty()) || (stream.eof())) { - if ((match.length(1) > 0) || (stream.eof())) { - is_blank = false; - } - } - } - - return stream; -} - -std::istream& StreamHandler::skip_to_next_blank_line(std::istream& stream, std::string& line) { - bool line_is_empty = false; - - const std::regex whitespace_re("\\s*(.*)"); - std::smatch match; - - while (!line_is_empty) { - utils::StreamHandler::get_line(stream, line); - - std::regex_search(line, match, whitespace_re); - - if ((match.length(1) == 0) || (stream.eof())) { - line_is_empty = true; - } - } - - return stream; -} - -std::string String::trim(const std::string& line) { - const char* WhiteSpace = " \t\v\r\n"; - std::size_t start = line.find_first_not_of(WhiteSpace); - std::size_t end = line.find_last_not_of(WhiteSpace); - return start == end ? std::string() : line.substr(start, end - start + 1); -} - -} // Namespace Utils +#include +#include +#include +#include +#include +#include + +namespace Utils { + +std::istream& StreamHandler::get_line(std::istream& stream, std::string& line) { + std::string newline; + + std::getline(stream, newline); + + // Windows CRLF (\r\n) + if (!newline.empty() && newline[newline.size() - 1] == '\r') { + line = newline.substr(0, newline.size() - 1); + // MacOS LF (\r) + } else if (!newline.empty() && newline[newline.size()] == '\r') { + line = newline.replace(newline.size(), 1, "\n"); + } else { + line = newline; + } + + return stream; +} + +std::istream& StreamHandler::get_next_non_blank_line(std::istream& stream, std::string& line) { + bool is_blank = true; + + const std::regex whitespace_re("\\s*(.*)"); + std::smatch match; + + while (is_blank) { + Utils::StreamHandler::get_line(stream, line); + + std::regex_search(line, match, whitespace_re); + + if ((!line.empty() && !match.empty()) || (stream.eof())) { + if ((match.length(1) > 0) || (stream.eof())) { + is_blank = false; + } + } + } + + return stream; +} + +std::istream& StreamHandler::skip_to_next_blank_line(std::istream& stream, std::string& line) { + bool line_is_empty = false; + + const std::regex whitespace_re("\\s*(.*)"); + std::smatch match; + + while (!line_is_empty) { + Utils::StreamHandler::get_line(stream, line); + + std::regex_search(line, match, whitespace_re); + + if ((match.length(1) == 0) || (stream.eof())) { + line_is_empty = true; + } + } + + return stream; +} + +std::string String::trim(const std::string& line) { + const char* WhiteSpace = " \t\v\r\n"; + std::size_t start = line.find_first_not_of(WhiteSpace); + std::size_t end = line.find_last_not_of(WhiteSpace); + return start == end ? std::string() : line.substr(start, end - start + 1); +} + +double String::convert_to_double(const std::string& value, double default_value) { + double converted_value = default_value; + // NOLINTNEXTLINE -- Trying to iterators on the value causes the test to infinitly hang on windows builds + fast_float::from_chars(value.data(), value.data() + value.size(), converted_value); + return converted_value; +} + +} // Namespace Utils diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d856c08..fc8cf97 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,35 +3,71 @@ enable_testing() # Download and build Catch2 test framework Include(FetchContent) FetchContent_Declare( - Catch2 - GIT_REPOSITORY https://github.com/catchorg/Catch2.git - GIT_TAG v3.2.1 + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v3.5.2 ) FetchContent_MakeAvailable(Catch2) include(Catch) -add_executable(dbcParserTests test_dbc.cpp test_utils.cpp common.cpp) -target_compile_definitions(dbcParserTests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") -target_link_libraries(dbcParserTests PRIVATE dbc Catch2::Catch2WithMain) -if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) - target_sources(dbcParserTests PRIVATE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} - FILES defines.hpp) +# Need filesystem for testing +set(CMAKE_CXX_STANDARD 17) + +if (MSVC) + add_compile_options(/W4 /WX) else() - target_include_directories(dbcParserTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + add_compile_options(-Wall -Wextra -Wpedantic -Werror) +endif() + +# Code coverage compiler specific +if (GCC) + add_compile_options(--coverage) endif() + + +add_executable(dbcParserTests + test_dbc.cpp + test_utils.cpp + test_parse_message.cpp + testing_utils/common.cpp +) + +target_compile_definitions(dbcParserTests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") +target_link_libraries(dbcParserTests PRIVATE dbc Catch2::Catch2WithMain) +target_include_directories(dbcParserTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + catch_discover_tests(dbcParserTests) -add_executable(dbcParserParsemessageTests test_parseMessage.cpp common.cpp) -target_compile_definitions(dbcParserParsemessageTests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") -target_link_libraries(dbcParserParsemessageTests PRIVATE dbc Catch2::Catch2WithMain) -if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) - target_sources(dbcParserParsemessageTests PRIVATE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} - FILES defines.hpp) +# We want a seperate binary for this test. We setup global locals which mess with all of the testing. +# Opting for a sperate test running so we don't conflict +if(DBC_TEST_LOCALE_INDEPENDENCE) + add_executable(dbcLocaleTests + locale_testing/test_locale_main.cpp + testing_utils/common.cpp + ) + + target_compile_definitions(dbcLocaleTests PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") + target_link_libraries(dbcLocaleTests PRIVATE dbc Catch2::Catch2WithMain) + target_include_directories(dbcLocaleTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +catch_discover_tests(dbcLocaleTests) else() - target_include_directories(dbcParserParsemessageTests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + message(WARNING "Locale independent testing is turned off!") +endif() + +# Again another test binary to ensure we aren't including our other headers. +# It should compile and run on one include +if(DBC_GENERATE_SINGLE_HEADER) + add_executable(dbcSingleHeaderTest + single_header_testing/test_single_header.cpp + testing_utils/common.cpp + ) + + target_compile_definitions(dbcSingleHeaderTest PRIVATE TESTDBCFILES_PATH="${CMAKE_CURRENT_SOURCE_DIR}/dbcs") + target_link_libraries(dbcSingleHeaderTest PRIVATE Catch2::Catch2WithMain) + target_include_directories(dbcSingleHeaderTest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/single_header/) + + catch_discover_tests(dbcSingleHeaderTest) + + add_dependencies(dbcSingleHeaderTest single_header) endif() -catch_discover_tests(dbcParserParsemessageTests) diff --git a/test/common.cpp b/test/common.cpp deleted file mode 100644 index 383ebac..0000000 --- a/test/common.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#include "common.hpp" -#include "defines.hpp" - -bool create_tmp_dbc_with(const char* filename, const char* content) { - auto* file = std::fopen(filename, "w"); - if (!file) { - return false; - } - - std::fputs(PRIMITIVE_DBC.c_str(), file); - std::fputs(content, file); - std::fclose(file); - return true; -} diff --git a/test/common.hpp b/test/common.hpp deleted file mode 100644 index 074fc25..0000000 --- a/test/common.hpp +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef COMMON_H -#define COMMON_H - -bool create_tmp_dbc_with(const char* filename, const char* content); - -#endif // COMMON_H diff --git a/test/dbcs/MissingVersion.dbc b/test/dbcs/MissingVersion.dbc index 0db087a..40d5967 100644 --- a/test/dbcs/MissingVersion.dbc +++ b/test/dbcs/MissingVersion.dbc @@ -1,36 +1,36 @@ -NS_ : - BA_ - BA_DEF_ - BA_DEF_DEF_ - BA_DEF_DEF_REL_ - BA_DEF_REL_ - BA_DEF_SGTYPE_ - BA_REL_ - BA_SGTYPE_ - BO_TX_BU_ - BU_BO_REL_ - BU_EV_REL_ - BU_SG_REL_ - CAT_ - CAT_DEF_ - CM_ - ENVVAR_DATA_ - EV_DATA_ - FILTER - NS_DESC_ - SGTYPE_ - SGTYPE_VAL_ - SG_MUL_VAL_ - SIGTYPE_VALTYPE_ - SIG_GROUP_ - SIG_TYPE_REF_ - SIG_VALTYPE_ - VAL_ - VAL_TABLE_ - -BS_: - -BU_: DBG DRIVER IO MOTOR SENSOR - -BO_ 500 IO_DEBUG: 4 IO - SG_ IO_DEBUG_test_unsigned : 0|8@1+ (1,0) [0|0] "" DBG \ No newline at end of file +NS_ : + BA_ + BA_DEF_ + BA_DEF_DEF_ + BA_DEF_DEF_REL_ + BA_DEF_REL_ + BA_DEF_SGTYPE_ + BA_REL_ + BA_SGTYPE_ + BO_TX_BU_ + BU_BO_REL_ + BU_EV_REL_ + BU_SG_REL_ + CAT_ + CAT_DEF_ + CM_ + ENVVAR_DATA_ + EV_DATA_ + FILTER + NS_DESC_ + SGTYPE_ + SGTYPE_VAL_ + SG_MUL_VAL_ + SIGTYPE_VALTYPE_ + SIG_GROUP_ + SIG_TYPE_REF_ + SIG_VALTYPE_ + VAL_ + VAL_TABLE_ + +BS_: + +BU_: DBG DRIVER IO MOTOR SENSOR + +BO_ 500 IO_DEBUG: 4 IO + SG_ IO_DEBUG_test_unsigned : 0|8@1+ (1,0) [0|0] "" DBG diff --git a/test/locale_testing/test_locale_main.cpp b/test/locale_testing/test_locale_main.cpp new file mode 100644 index 0000000..537a81f --- /dev/null +++ b/test/locale_testing/test_locale_main.cpp @@ -0,0 +1,80 @@ +#include "testing_utils/common.hpp" +#include "testing_utils/defines.hpp" +#include +#include + +#include +#include +#include +#include + +class testRunListener : public Catch::EventListenerBase { +public: + using Catch::EventListenerBase::EventListenerBase; + + void testRunStarting(Catch::TestRunInfo const&) override { + // Mac OS uses global and c++ standard uses the std. Using this to remove ambiguity between the two. + prev_loc = ::setlocale(LC_ALL, nullptr); + // Set the locale to something that has , instead of . for floats + std::locale::global(std::locale("de_DE.UTF-8")); + } + + void testCaseEnded(Catch::TestCaseStats const&) override { + // Restore the old locale + std::locale::global(std::locale(prev_loc)); + } + +private: + std::string prev_loc; +}; + +CATCH_REGISTER_LISTENER(testRunListener) + +TEST_CASE("Should parse doubld string locale independently") { + REQUIRE(Catch::Approx(Utils::String::convert_to_double("6.82")) == 6.82); +} + +TEST_CASE("Should process message with floats locale indpendently") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig3 : 23|16@0- (10,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 4); + + SECTION("Evaluating first message") { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.factor == 0.1); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating second message") { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.factor == 0.1); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating third message") { + const auto signal = parser.get_messages().at(0).get_signals().at(2); + REQUIRE(signal.factor == 10); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating fourth message") { + const auto signal = parser.get_messages().at(0).get_signals().at(3); + REQUIRE(signal.factor == 1); + REQUIRE(signal.offset == -10); + REQUIRE(signal.min == 0); + REQUIRE(signal.max == 32767); + } +} diff --git a/test/single_header_testing/test_single_header.cpp b/test/single_header_testing/test_single_header.cpp new file mode 100644 index 0000000..c512d35 --- /dev/null +++ b/test/single_header_testing/test_single_header.cpp @@ -0,0 +1,80 @@ +#include "testing_utils/common.hpp" +#include "testing_utils/defines.hpp" +#include + +#include +#include +#include + +TEST_CASE("Testing dbc file loading", "[fileio]") { + auto parser = std::unique_ptr(new Libdbc::DbcParser()); + + SECTION("Loading a single simple dbc file", "[dbc]") { + std::vector nodes = {"DBG", "DRIVER", "IO", "MOTOR", "SENSOR"}; + + Libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); + + std::vector receivers{"DBG"}; + Libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); + msg.append_signal(sig); + + std::vector msgs = {msg}; + + parser->parse_file(SIMPLE_DBC_FILE); + + REQUIRE(parser->get_version() == "1.0.0"); + + REQUIRE(parser->get_nodes() == nodes); + + REQUIRE(parser->get_messages() == msgs); + + REQUIRE(parser->get_messages().front().get_signals() == msg.get_signals()); + } +} + +TEST_CASE("Testing big endian, little endian") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 8); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.is_bigendian == true); + } + { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.is_bigendian == false); + } +} + +TEST_CASE("Testing file stream mirrors the filename interface") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + std::ifstream file(filename.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(file); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 8); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.is_bigendian == true); + } + { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.is_bigendian == false); + } +} diff --git a/test/test_dbc.cpp b/test/test_dbc.cpp index 1c218aa..8ae1b54 100644 --- a/test/test_dbc.cpp +++ b/test/test_dbc.cpp @@ -1,154 +1,311 @@ -#include "common.hpp" -#include "defines.hpp" -#include -#include -#include -#include - -TEST_CASE("Testing dbc file loading error issues", "[fileio][error]") { - auto parser = std::unique_ptr(new libdbc::DbcParser()); - - SECTION("Loading a non dbc file should throw an error", "[error]") { - REQUIRE_THROWS_AS(parser->parse_file(TEXT_FILE), libdbc::validity_error); - } - - SECTION("Loading a dbc with bad headers throws an error", "[error]") { - REQUIRE_THROWS_AS(parser->parse_file(MISSING_VERSION_DBC_FILE), libdbc::validity_error); - } - - SECTION("Loading a dbc without the required bit timing section (BS_:)", "[error]") { - REQUIRE_THROWS_AS(parser->parse_file(MISSING_BIT_TIMING_DBC_FILE), libdbc::validity_error); - } - - SECTION("Loading a dbc with some missing namespace section tags (NS_ :)", "[error]") { - // Confusion about this type of error. it appears that the header isn't - // very well standardized for now we ignore this type of error. - CHECK_NOTHROW(parser->parse_file(MISSING_NEW_SYMBOLS_DBC_FILE)); - } - - SECTION("Verify that what() method is accessible for all exceptions", "[error]") { - auto generic_error = libdbc::exception(); - REQUIRE(generic_error.what() == "libdbc exception occurred"); - - auto validity_check = libdbc::validity_error(); - REQUIRE(validity_check.what() == "Invalid DBC file"); - } -} - -TEST_CASE("Testing dbc file loading", "[fileio]") { - auto parser = std::unique_ptr(new libdbc::DbcParser()); - - SECTION("Loading a single simple dbc file", "[dbc]") { - std::vector nodes = {"DBG", "DRIVER", "IO", "MOTOR", "SENSOR"}; - - libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); - - std::vector receivers{"DBG"}; - libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); - msg.appendSignal(sig); - - std::vector msgs = {msg}; - - parser->parse_file(SIMPLE_DBC_FILE); - - REQUIRE(parser->get_version() == "1.0.0"); - - REQUIRE(parser->get_nodes() == nodes); - - REQUIRE(parser->get_messages() == msgs); - - REQUIRE(parser->get_messages().front().signals() == msg.signals()); - } -} - -TEST_CASE("Testing big endian, little endian") { - const auto* filename = std::tmpnam(NULL); - - auto* file = std::fopen(filename, "w"); - CHECK(file); - - std::fputs(PRIMITIVE_DBC.c_str(), file); - // first big endian - // second little endian - std::fputs(R"(BO_ 234 MSG1: 8 Vector__XXX - SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)", - file); - std::fclose(file); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals().size() == 2); - { - const auto signal = parser.get_messages().at(0).signals().at(0); - REQUIRE(signal.is_bigendian == true); - } - { - const auto signal = parser.get_messages().at(0).signals().at(1); - REQUIRE(signal.is_bigendian == false); - } -} - -TEST_CASE("Testing negative values") { - const auto* filename = std::tmpnam(NULL); - - create_tmp_dbc_with(filename, R"(BO_ 234 MSG1: 8 Vector__XXX - SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig2 : 39|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig3 : 23|16@0- (10,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals().size() == 4); - - SECTION("Evaluating first message") { - const auto signal = parser.get_messages().at(0).signals().at(0); - REQUIRE(signal.factor == 0.1); - REQUIRE(signal.offset == 0); - REQUIRE(signal.min == -3276.8); - REQUIRE(signal.max == -3276.7); - } - SECTION("Evaluating second message") { - const auto signal = parser.get_messages().at(0).signals().at(1); - REQUIRE(signal.factor == 0.1); - REQUIRE(signal.offset == 0); - REQUIRE(signal.min == -3276.8); - REQUIRE(signal.max == -3276.7); - } - SECTION("Evaluating third message") { - const auto signal = parser.get_messages().at(0).signals().at(2); - REQUIRE(signal.factor == 10); - REQUIRE(signal.offset == 0); - REQUIRE(signal.min == -3276.8); - REQUIRE(signal.max == -3276.7); - } - SECTION("Evaluating fourth message") { - const auto signal = parser.get_messages().at(0).signals().at(3); - REQUIRE(signal.factor == 1); - REQUIRE(signal.offset == -10); - REQUIRE(signal.min == 0); - REQUIRE(signal.max == 32767); - } -} - -TEST_CASE("Special characters in unit") { - const auto* filename = std::tmpnam(NULL); - - create_tmp_dbc_with(filename, R"(BO_ 234 MSG1: 8 Vector__XXX - SG_ Speed : 0|8@1+ (1,0) [0|204] "Km/h" DEVICE1,DEVICE2,DEVICE3)"); - - auto parser = libdbc::DbcParser(); - parser.parse_file(filename); - - REQUIRE(parser.get_messages().size() == 1); - REQUIRE(parser.get_messages().at(0).signals().size() == 1); - SECTION("Checking that signal with special characters as unit is parsed correctly") { - const auto signal = parser.get_messages().at(0).signals().at(0); - REQUIRE(signal.unit.compare("Km/h") == 0); - } -} +#include "testing_utils/common.hpp" +#include "testing_utils/defines.hpp" +#include +#include +#include +#include +#include +#include +#include + +using Catch::Matchers::ContainsSubstring; + +TEST_CASE("Testing dbc file loading error issues", "[fileio][error]") { + auto parser = std::unique_ptr(new Libdbc::DbcParser()); + + SECTION("Loading a non dbc file should throw an error", "[error]") { + REQUIRE_THROWS_AS(parser->parse_file(TEXT_FILE), Libdbc::NonDbcFileFormatError); + REQUIRE_THROWS_WITH(parser->parse_file(TEXT_FILE), ContainsSubstring("TextFile.txt")); + } + + SECTION("Loading a dbc with missing version header throws an error (VERSION)", "[error]") { + REQUIRE_THROWS_AS(parser->parse_file(MISSING_VERSION_DBC_FILE), Libdbc::DbcFileIsMissingVersion); + REQUIRE_THROWS_WITH(parser->parse_file(MISSING_VERSION_DBC_FILE), ContainsSubstring("line: (NS_ :)")); + } + + SECTION("Loading a dbc without the required bit timing section (BS_:)", "[error]") { + REQUIRE_THROWS_AS(parser->parse_file(MISSING_BIT_TIMING_DBC_FILE), Libdbc::DbcFileIsMissingBitTiming); + REQUIRE_THROWS_WITH(parser->parse_file(MISSING_BIT_TIMING_DBC_FILE), ContainsSubstring("BU_: DBG DRIVER IO MOTOR SENSOR")); + } + + SECTION("Loading a dbc with some missing namespace section tags (NS_ :)", "[error]") { + // Confusion about this type of error. it appears that the header isn't + // very well standardized for now we ignore this type of error. + REQUIRE_NOTHROW(parser->parse_file(MISSING_NEW_SYMBOLS_DBC_FILE)); + } + + SECTION("Verify that what() method is accessible for all exceptions", "[error]") { + auto generic_error = Libdbc::Exception(); + REQUIRE(std::string{generic_error.what()} == "libdbc exception occurred"); + + auto validity_check = Libdbc::ValidityError(); + REQUIRE(std::string{validity_check.what()} == "Invalid DBC file"); + } +} + +TEST_CASE("Testing dbc file loading", "[fileio]") { + auto parser = std::unique_ptr(new Libdbc::DbcParser()); + + SECTION("Loading a single simple dbc file", "[dbc]") { + std::vector nodes = {"DBG", "DRIVER", "IO", "MOTOR", "SENSOR"}; + + Libdbc::Message msg(500, "IO_DEBUG", 4, "IO"); + + std::vector receivers{"DBG"}; + Libdbc::Signal sig("IO_DEBUG_test_unsigned", false, 0, 8, false, false, 1, 0, 0, 0, "", receivers); + msg.append_signal(sig); + + std::vector msgs = {msg}; + + parser->parse_file(SIMPLE_DBC_FILE); + + REQUIRE(parser->get_version() == "1.0.0"); + + REQUIRE(parser->get_nodes() == nodes); + + REQUIRE(parser->get_messages() == msgs); + + REQUIRE(parser->get_messages().front().get_signals() == msg.get_signals()); + } +} + +TEST_CASE("Testing big endian, little endian") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@1- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 8); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.is_bigendian == true); + } + { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.is_bigendian == false); + } +} + +TEST_CASE("Testing negative values") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 58 Vector__XXX + SG_ Sig1 : 55|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig2 : 39|16@0- (0.1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig3 : 23|16@0- (10,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Sig4 : 7|16@0- (1,-10) [0|32767] "" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 58); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 4); + + SECTION("Evaluating first message") { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.factor == 0.1); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating second message") { + const auto signal = parser.get_messages().at(0).get_signals().at(1); + REQUIRE(signal.factor == 0.1); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating third message") { + const auto signal = parser.get_messages().at(0).get_signals().at(2); + REQUIRE(signal.factor == 10); + REQUIRE(signal.offset == 0); + REQUIRE(signal.min == -3276.8); + REQUIRE(signal.max == -3276.7); + } + SECTION("Evaluating fourth message") { + const auto signal = parser.get_messages().at(0).get_signals().at(3); + REQUIRE(signal.factor == 1); + REQUIRE(signal.offset == -10); + REQUIRE(signal.min == 0); + REQUIRE(signal.max == 32767); + } +} + +TEST_CASE("Special characters in unit") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 255 Vector__XXX + SG_ Speed : 0|8@1+ (1,0) [0|204] "Km/h" DEVICE1,DEVICE2,DEVICE3)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).size() == 255); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 1); + SECTION("Checking that signal with special characters as unit is parsed correctly") { + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.unit.compare("Km/h") == 0); + } +} + +TEST_CASE("Signal Value Description") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!" ;)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + + REQUIRE(parser.get_messages().at(0).get_signals().at(0).value_descriptions.size() == 3); + REQUIRE(parser.get_messages().at(0).get_signals().at(1).value_descriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.value_descriptions.at(0).value == 123); + REQUIRE(signal.value_descriptions.at(0).description == "Description 1"); + REQUIRE(signal.value_descriptions.at(1).value == 0); + REQUIRE(signal.value_descriptions.at(1).description == "Description 2"); + REQUIRE(signal.value_descriptions.at(2).value == 90903489); + REQUIRE(signal.value_descriptions.at(2).description == "Big value and special characters &$§())!"); +} + +TEST_CASE("Signal Value Description Extended CAN id") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 3221225472 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" 4000000000 "Big value and special characters &$§())!" ;)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 1); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + + REQUIRE(parser.get_messages().at(0).get_signals().at(0).value_descriptions.size() == 3); + REQUIRE(parser.get_messages().at(0).get_signals().at(1).value_descriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.value_descriptions.at(0).value == 123); + REQUIRE(signal.value_descriptions.at(0).description == "Description 1"); + REQUIRE(signal.value_descriptions.at(1).value == 0); + REQUIRE(signal.value_descriptions.at(1).description == "Description 2"); + REQUIRE(signal.value_descriptions.at(2).value == 4000000000); + REQUIRE(signal.value_descriptions.at(2).description == "Big value and special characters &$§())!"); +} + +TEST_CASE("Signal Value Multiple VAL_") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 3221225472 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3" +BO_ 123 MSG2: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 3221225472 State1 123 "Description 1" 0 "Description 2" ; +VAL_ 123 State1 123 "Description 3" 0 "Description 4" ;)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + auto parser = Libdbc::DbcParser(); + parser.parse_file(filename.c_str()); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages().at(0).name() == "MSG1"); + REQUIRE(parser.get_messages().at(1).name() == "MSG2"); + + REQUIRE(parser.get_messages().at(0).get_signals().size() == 2); + + REQUIRE(parser.get_messages().at(0).get_signals().at(0).value_descriptions.size() == 2); + REQUIRE(parser.get_messages().at(0).get_signals().at(1).value_descriptions.size() == 0); + REQUIRE(parser.get_messages().at(1).get_signals().at(0).value_descriptions.size() == 2); + REQUIRE(parser.get_messages().at(1).get_signals().at(1).value_descriptions.size() == 0); + + const auto signal = parser.get_messages().at(0).get_signals().at(0); + REQUIRE(signal.value_descriptions.at(0).value == 123); + REQUIRE(signal.value_descriptions.at(0).description == "Description 1"); + REQUIRE(signal.value_descriptions.at(1).value == 0); + REQUIRE(signal.value_descriptions.at(1).description == "Description 2"); + + const auto signal2 = parser.get_messages().at(1).get_signals().at(0); + REQUIRE(signal2.value_descriptions.at(0).value == 123); + REQUIRE(signal2.value_descriptions.at(0).description == "Description 3"); + REQUIRE(signal2.value_descriptions.at(1).value == 0); + REQUIRE(signal2.value_descriptions.at(1).description == "Description 4"); +} + +TEST_CASE("Should parse DBC with empty BU_", "[error][optional]") { + std::string contents = R"(VERSION "" + + +NS_ : + +BS_: + +BU_: + + +BO_ 293 Msg1: 2 Vector__XXX + SG_ Wert7 : 0|16@1- (1,0) [0|0] "" Vector__XXX + +BO_ 292 Msg2: 1 Vector__XXX + SG_ Wert8 : 56|8@1- (1,0) [0|0] "" Vector__XXX +)"; + const auto filename = create_temporary_dbc_with(contents.c_str()); + + auto parser = Libdbc::DbcParser(); + REQUIRE_NOTHROW(parser.parse_file(filename.c_str())); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages().at(0).name() == "Msg1"); + REQUIRE(parser.get_messages().at(1).name() == "Msg2"); +} + +TEST_CASE("Should report unused lines since we don't have tracing.", "[parsing]") { + std::string contents = R"(VERSION "" + +NS_ : + +BS_: + +BU_: + + +BO_ 293 Msg1: 2 Vector__XXX + SG_ Whitespace: | 0|16@1- (1,0) [0|0] "" Vector__XXX + SG_ Wert7 : 0|16@1- (1,0) [0|0] "" Vector__XXX + SG_ Wert8 : 0|16@1- (1,0) [0|0] "" Vector__XXX + +BO_ 292 Msg2: 1 Vector__XXX + SG_ Wert8 : 56|8@1- (1,0) [0|0] "" Vector__XXX + SB_ not a correct line + +BO_ have a issue here: +)"; + + const auto filename = create_temporary_dbc_with(contents.c_str()); + + auto parser = Libdbc::DbcParser(); + REQUIRE_NOTHROW(parser.parse_file(filename.c_str())); + + REQUIRE(parser.get_messages().size() == 2); + REQUIRE(parser.get_messages()[0].size() == 2); + REQUIRE(parser.get_messages()[1].size() == 1); + + auto unused = parser.unused_lines(); + + // We could match them all here but i think just a check that the size is sufficent. + REQUIRE(unused.size() == 3); +} diff --git a/test/test_parseMessage.cpp b/test/test_parseMessage.cpp deleted file mode 100644 index e171484..0000000 --- a/test/test_parseMessage.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include -#include -#include - -#include - -#include "common.hpp" - -// Testing of parsing messages - -TEST_CASE("Parse Message Unknown ID") { - libdbc::DbcParser parser; - - const auto dbcContent = R"(BO_ 234 MSG1: 8 Vector__XXX - SG_ Msg1Sig1 : 0|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ MsgSig2 : 8|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX -BO_ 123 MSG2: 8 Vector__XXX - SG_ Msg2Sig1 : 0|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX - SG_ Msg2Sig1 : 8|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX -)"; - - const auto* filename = std::tmpnam(NULL); - CHECK(create_tmp_dbc_with(filename, dbcContent)); - - parser.parse_file(filename); - - SECTION("Evaluating unknown message id") { - std::vector out_values; - CHECK(parser.parseMessage(578, std::vector({0xFF, 0xA2}), out_values) == libdbc::Message::ParseSignalsStatus::ErrorUnknownID); - } -} - -TEST_CASE("Parse Message Big Number not aligned little endian") { - libdbc::DbcParser parser; - - const auto dbcContent = R"(BO_ 337 STATUS: 8 Vector__XXX - SG_ Value6 : 27|3@1+ (1,0) [0|7] "" Vector__XXX - SG_ Value5 : 16|11@1+ (0.1,-102) [-102|102] "%" Vector__XXX - SG_ Value2 : 8|2@1+ (1,0) [0|2] "" Vector__XXX - SG_ Value3 : 10|1@1+ (1,0) [0|1] "" Vector__XXX - SG_ Value7 : 30|2@1+ (1,0) [0|3] "" Vector__XXX - SG_ Value4 : 11|4@1+ (1,0) [0|3] "" Vector__XXX - SG_ Value1 : 0|8@1+ (1,0) [0|204] "Km/h" Vector__XXX -)"; - - const auto* filename = std::tmpnam(NULL); - CHECK(create_tmp_dbc_with(filename, dbcContent)); - - parser.parse_file(filename); - - SECTION("Evaluating first message") { - std::vector out_values; - CHECK(parser.parseMessage(337, std::vector({0, 4, 252, 19, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); - std::vector refData{2, 0, 0, 1, 0, 0, 0}; - CHECK(refData.size() == 7); - CHECK(out_values.size() == refData.size()); - for (int i = 0; i < refData.size(); i++) { - CHECK(out_values.at(i) == refData.at(i)); - } - } - - SECTION("Evaluating second message") { - std::vector out_values; - CHECK(parser.parseMessage(337, std::vector({47, 4, 60, 29, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); - std::vector refData{3, 32, 0, 1, 0, 0, 47}; - CHECK(refData.size() == 7); - CHECK(out_values.size() == refData.size()); - for (int i = 0; i < refData.size(); i++) { - CHECK(out_values.at(i) == refData.at(i)); - } - } - - SECTION("Evaluating third message") { - std::vector out_values; - CHECK(parser.parseMessage(337, std::vector({57, 4, 250, 29, 0, 0, 0, 0}), out_values) == libdbc::Message::ParseSignalsStatus::Success); - std::vector refData{3, 51, 0, 1, 0, 0, 57}; - CHECK(refData.size() == 7); - CHECK(out_values.size() == refData.size()); - for (int i = 0; i < refData.size(); i++) { - CHECK(out_values.at(i) == refData.at(i)); - } - } -} - -TEST_CASE("Parse Message little endian") { - const auto* filename = std::tmpnam(NULL); - - create_tmp_dbc_with(filename, R"(BO_ 541 STATUS: 8 DEVICE1 - SG_ Temperature : 48|16@1+ (0.01,-40) [-40|125] "C" DEVICE1 - SG_ SOH : 0|16@1+ (0.01,0) [0|100] "%" DEVICE1 - SG_ SOE : 32|16@1+ (0.01,0) [0|100] "%" DEVICE1 - SG_ SOC : 16|16@1+ (0.01,0) [0|100] "%" DEVICE1)"); - - libdbc::DbcParser p; - p.parse_file(filename); - - std::vector data{0x08, 0x27, 0xa3, 0x22, 0xe5, 0x1f, 0x45, 0x14}; // little endian - std::vector result_values; - REQUIRE(p.parseMessage(0x21d, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); - REQUIRE(result_values.size() == 4); - - REQUIRE(Catch::Approx(result_values.at(0)) == 11.89); - REQUIRE(Catch::Approx(result_values.at(1)) == 99.92); - REQUIRE(Catch::Approx(result_values.at(2)) == 81.65); - REQUIRE(Catch::Approx(result_values.at(3)) == 88.67); -} - -TEST_CASE("Parse Message big endian signed values") { - const auto* filename = std::tmpnam(NULL); - create_tmp_dbc_with(filename, R"(BO_ 545 MSG: 8 BMS2 - SG_ Sig1 : 62|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig2 : 49|2@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig3 : 39|16@0- (0.1,0) [0|0] "A" Vector__XXX - SG_ Sig4 : 60|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig5 : 55|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig6 : 58|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig7 : 59|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig8 : 57|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig9 : 56|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig10 : 61|1@0+ (1,0) [0|0] "" Vector__XXX - SG_ Sig11 : 7|16@0+ (0.001,0) [0|65.535] "V" Vector__XXX - SG_ Sig12 : 23|16@0+ (0.1,0) [0|6553.5] "A" Vector__XXX)"); - - libdbc::DbcParser p; - p.parse_file(filename); - - std::vector data{13, 177, 0, 216, 251, 180, 0, 31}; // big endian - std::vector result_values; - REQUIRE(p.parseMessage(545, data, result_values) == libdbc::Message::ParseSignalsStatus::Success); - REQUIRE(result_values.size() == 12); - REQUIRE(Catch::Approx(result_values.at(0)) == 0); - REQUIRE(Catch::Approx(result_values.at(1)) == 0); - REQUIRE(Catch::Approx(result_values.at(2)) == -110); - REQUIRE(Catch::Approx(result_values.at(3)) == 1); - REQUIRE(Catch::Approx(result_values.at(4)) == 0); - REQUIRE(Catch::Approx(result_values.at(5)) == 1); - REQUIRE(Catch::Approx(result_values.at(6)) == 1); - REQUIRE(Catch::Approx(result_values.at(7)) == 1); - REQUIRE(Catch::Approx(result_values.at(8)) == 1); - REQUIRE(Catch::Approx(result_values.at(9)) == 0); - REQUIRE(Catch::Approx(result_values.at(10)) == 3.5050); - REQUIRE(Catch::Approx(result_values.at(11)) == 21.6); -} diff --git a/test/test_parse_message.cpp b/test/test_parse_message.cpp new file mode 100644 index 0000000..08d68f3 --- /dev/null +++ b/test/test_parse_message.cpp @@ -0,0 +1,211 @@ +#include +#include +#include + +#include + +#include "testing_utils/common.hpp" +#include "testing_utils/defines.hpp" + +// Testing of parsing messages + +TEST_CASE("Parse Message Unknown ID") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Msg1Sig1 : 0|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ MsgSig2 : 8|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX +BO_ 123 MSG2: 8 Vector__XXX + SG_ Msg2Sig1 : 0|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Msg2Sig1 : 8|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX +)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser parser; + parser.parse_file(filename.c_str()); + + SECTION("Evaluating unknown` message id") { + std::vector out_values; + CHECK(parser.parse_message(578, std::vector({0xFF, 0xA2}), out_values) == Libdbc::Message::ParseSignalsStatus::ErrorUnknownID); + } +} + +TEST_CASE("Parse Message Big Number not aligned little endian") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 337 STATUS: 8 Vector__XXX + SG_ Value6 : 27|3@1+ (1,0) [0|7] "" Vector__XXX + SG_ Value5 : 16|11@1+ (0.1,-102) [-102|102] "%" Vector__XXX + SG_ Value2 : 8|2@1+ (1,0) [0|2] "" Vector__XXX + SG_ Value3 : 10|1@1+ (1,0) [0|1] "" Vector__XXX + SG_ Value7 : 30|2@1+ (1,0) [0|3] "" Vector__XXX + SG_ Value4 : 11|4@1+ (1,0) [0|3] "" Vector__XXX + SG_ Value1 : 0|8@1+ (1,0) [0|204] "Km/h" Vector__XXX +)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser parser; + parser.parse_file(filename); + + SECTION("Evaluating first message") { + std::vector out_values; + CHECK(parser.parse_message(337, std::vector({0, 4, 252, 19, 0, 0, 0, 0}), out_values) == Libdbc::Message::ParseSignalsStatus::Success); + std::vector refData{2, 0, 0, 1, 0, 0, 0}; + CHECK(refData.size() == 7); + CHECK(out_values.size() == refData.size()); + for (size_t i = 0; i < refData.size(); i++) { + CHECK(out_values.at(i) == refData.at(i)); + } + } + + SECTION("Evaluating second message") { + std::vector out_values; + CHECK(parser.parse_message(337, std::vector({47, 4, 60, 29, 0, 0, 0, 0}), out_values) == Libdbc::Message::ParseSignalsStatus::Success); + std::vector refData{3, 32, 0, 1, 0, 0, 47}; + CHECK(refData.size() == 7); + CHECK(out_values.size() == refData.size()); + for (size_t i = 0; i < refData.size(); i++) { + CHECK(out_values.at(i) == refData.at(i)); + } + } + + SECTION("Evaluating third message") { + std::vector out_values; + CHECK(parser.parse_message(337, std::vector({57, 4, 250, 29, 0, 0, 0, 0}), out_values) == Libdbc::Message::ParseSignalsStatus::Success); + std::vector refData{3, 51, 0, 1, 0, 0, 57}; + CHECK(refData.size() == 7); + CHECK(out_values.size() == refData.size()); + for (size_t i = 0; i < refData.size(); i++) { + CHECK(out_values.at(i) == refData.at(i)); + } + } +} + +TEST_CASE("Parse Message little endian") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 541 STATUS: 8 DEVICE1 + SG_ Temperature : 48|16@1+ (0.01,-40) [-40|125] "C" DEVICE1 + SG_ SOH : 0|16@1+ (0.01,0) [0|100] "%" DEVICE1 + SG_ SOE : 32|16@1+ (0.01,0) [0|100] "%" DEVICE1 + SG_ SOC : 16|16@1+ (0.01,0) [0|100] "%" DEVICE1)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser parser; + parser.parse_file(filename); + + std::vector data{0x08, 0x27, 0xa3, 0x22, 0xe5, 0x1f, 0x45, 0x14}; // little endian + std::vector result_values; + REQUIRE(parser.parse_message(0x21d, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 4); + + REQUIRE(Catch::Approx(result_values.at(0)) == 11.89); + REQUIRE(Catch::Approx(result_values.at(1)) == 99.92); + REQUIRE(Catch::Approx(result_values.at(2)) == 81.65); + REQUIRE(Catch::Approx(result_values.at(3)) == 88.67); +} + +TEST_CASE("Parse Message big endian signed values") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 545 MSG: 8 BMS2 + SG_ Sig1 : 62|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig2 : 49|2@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig3 : 39|16@0- (0.1,0) [0|0] "A" Vector__XXX + SG_ Sig4 : 60|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig5 : 55|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig6 : 58|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig7 : 59|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig8 : 57|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig9 : 56|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig10 : 61|1@0+ (1,0) [0|0] "" Vector__XXX + SG_ Sig11 : 7|16@0+ (0.001,0) [0|65.535] "V" Vector__XXX + SG_ Sig12 : 23|16@0+ (0.1,0) [0|6553.5] "A" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename.c_str()); + + std::vector data{13, 177, 0, 216, 251, 180, 0, 31}; // big endian + std::vector result_values; + REQUIRE(p.parse_message(545, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 12); + REQUIRE(Catch::Approx(result_values.at(0)) == 0); + REQUIRE(Catch::Approx(result_values.at(1)) == 0); + REQUIRE(Catch::Approx(result_values.at(2)) == -110); + REQUIRE(Catch::Approx(result_values.at(3)) == 1); + REQUIRE(Catch::Approx(result_values.at(4)) == 0); + REQUIRE(Catch::Approx(result_values.at(5)) == 1); + REQUIRE(Catch::Approx(result_values.at(6)) == 1); + REQUIRE(Catch::Approx(result_values.at(7)) == 1); + REQUIRE(Catch::Approx(result_values.at(8)) == 1); + REQUIRE(Catch::Approx(result_values.at(9)) == 0); + REQUIRE(Catch::Approx(result_values.at(10)) == 3.5050); + REQUIRE(Catch::Approx(result_values.at(11)) == 21.6); +} + +TEST_CASE("Parse Message with non byte aligned values") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 403 INFORMATION: 8 Vector__XXX + SG_ Voltage : 30|9@1+ (0.2,0) [0|102.2] "V" Vector__XXX + SG_ Phase_Current : 20|10@1- (1,0) [-512|512] "A" Vector__XXX + SG_ Iq_Current : 10|10@1- (1,0) [-512|512] "A" Vector__XXX + SG_ Id_Current : 0|10@1- (1,0) [-512|512] "A" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{131, 51, 33, 9, 33, 0, 0, 0}; + std::vector result_values; + REQUIRE(p.parse_message(403, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 4); + REQUIRE(Catch::Approx(result_values.at(0)) == 26.4); + REQUIRE(Catch::Approx(result_values.at(1)) == 146); + REQUIRE(Catch::Approx(result_values.at(2)) == 76); + REQUIRE(Catch::Approx(result_values.at(3)) == -125); +} + +TEST_CASE("Parse Message data length < 8 unsigned") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ Msg1Sig1 : 7|8@0+ (1,0) [-3276.8|-3276.7] "C" Vector__XXX + SG_ Msg1Sig2 : 15|8@0+ (1,0) [-3276.8|-3276.7] "km/h" Vector__XXX)"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{0x1, 0x2}; + std::vector result_values; + REQUIRE(p.parse_message(234, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 2); + REQUIRE(Catch::Approx(result_values.at(0)) == 0x1); + REQUIRE(Catch::Approx(result_values.at(1)) == 0x2); +} + +TEST_CASE("Parse message with BO_ on single line should fail.") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ +234 MSG1: 8 Vector__XXX + SG_ State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!")"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{0x1, 0x2}; + std::vector result_values; + REQUIRE(p.get_messages().size() == 0); + REQUIRE(p.parse_message(234, data, result_values) == Libdbc::Message::ParseSignalsStatus::ErrorUnknownID); +} + +TEST_CASE("Parse signal with SG_ on single line should fail.") { + std::string dbc_contents = PRIMITIVE_DBC + R"(BO_ 234 MSG1: 8 Vector__XXX + SG_ +State1 : 0|8@1+ (1,0) [0|200] "Km/h" DEVICE1,DEVICE2,DEVICE3 + SG_ State2 : 0|8@1+ (1,0) [0|204] "" DEVICE1,DEVICE2,DEVICE3 +VAL_ 234 State1 123 "Description 1" 0 "Description 2" 90903489 "Big value and special characters &$§())!")"; + const auto filename = create_temporary_dbc_with(dbc_contents.c_str()); + + Libdbc::DbcParser p; + p.parse_file(filename); + + std::vector data{0x1, 0x2}; + std::vector result_values; + REQUIRE(p.get_messages().size() == 1); + REQUIRE(p.parse_message(234, data, result_values) == Libdbc::Message::ParseSignalsStatus::Success); + REQUIRE(result_values.size() == 1); + REQUIRE(Catch::Approx(result_values.at(0)) == 0x1); +} diff --git a/test/test_utils.cpp b/test/test_utils.cpp index 6417498..af4929c 100644 --- a/test/test_utils.cpp +++ b/test/test_utils.cpp @@ -1,93 +1,95 @@ -#include "defines.hpp" -#include -#include - -#include - -using namespace utils; - -TEST_CASE("Basic file input with safe get_line that is non line ending specific", "") { - SECTION("Verify various line ending input files") { - std::ifstream TextFile; - std::string test; - - TextFile.open(TEXT_FILE, std::ios::in); - CHECK(TextFile.is_open()); - - if (TextFile.is_open()) { - StreamHandler::get_line(TextFile, test); - REQUIRE(test == "This is a non dbc formatted file."); - StreamHandler::get_line(TextFile, test); - REQUIRE(test == ""); - StreamHandler::get_line(TextFile, test); - REQUIRE(test == "Make sure things pass with this"); - StreamHandler::get_line(TextFile, test); - REQUIRE(test == "Who knows what might happen."); - - TextFile.close(); - } - } -} - -TEST_CASE("Test line finding utility functions", "") { - std::string line; - std::string test_string = - "hello\n\ - \n\ - \n\ - \n\ -this is not blank\n\ -maybe not this one either\n\ -\n\ -Someone wrote something....\n\ - b\n\ -end"; - - std::istringstream stream(test_string); - - SECTION("Test skipping empty lines") { - StreamHandler::get_line(stream, line); - - CHECK(line == "hello"); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == "this is not blank"); - - StreamHandler::skip_to_next_blank_line(stream, line); - REQUIRE(line == ""); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == "Someone wrote something...."); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == " b"); - - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == "end"); - - SECTION("Test end of the files", "[edge case]") { - StreamHandler::get_next_non_blank_line(stream, line); - REQUIRE(line == ""); - - StreamHandler::skip_to_next_blank_line(stream, line); - REQUIRE(line == ""); - } - } -} - -TEST_CASE("Test the string trim feature", "[string]") { - std::string s = " there might be some white space.... "; - - REQUIRE(String::trim(s) == "there might be some white space...."); -} - -TEST_CASE("Test string split feature", "[string]") { - std::string s = "name1 name2 name3 name4 name5 "; - std::vector vs = {"name1", "name2", "name3", "name4", "name5"}; - - std::vector v; - - String::split(s, v); - - REQUIRE(v == vs); -} +#include "testing_utils/defines.hpp" +#include +#include +#include +#include + +namespace Utils { + +TEST_CASE("Basic file input with safe get_line that is non line ending specific", "") { + SECTION("Verify various line ending input files") { + std::ifstream TextFile; + std::string test; + + TextFile.open(TEXT_FILE, std::ios::in); + CHECK(TextFile.is_open()); + + if (TextFile.is_open()) { + StreamHandler::get_line(TextFile, test); + REQUIRE(test == "This is a non dbc formatted file."); + StreamHandler::get_line(TextFile, test); + REQUIRE(test == ""); + StreamHandler::get_line(TextFile, test); + REQUIRE(test == "Make sure things pass with this"); + StreamHandler::get_line(TextFile, test); + REQUIRE(test == "Who knows what might happen."); + + TextFile.close(); + } + } +} + +TEST_CASE("Test line finding utility functions", "") { + std::string line; + std::string test_string = + "hello\n\ + \n\ + \n\ + \n\ +this is not blank\n\ +maybe not this one either\n\ +\n\ +Someone wrote something....\n\ + b\n\ +end"; + + std::istringstream stream(test_string); + + SECTION("Test skipping empty lines") { + StreamHandler::get_line(stream, line); + + CHECK(line == "hello"); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == "this is not blank"); + + StreamHandler::skip_to_next_blank_line(stream, line); + REQUIRE(line == ""); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == "Someone wrote something...."); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == " b"); + + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == "end"); + + SECTION("Test end of the files", "[edge case]") { + StreamHandler::get_next_non_blank_line(stream, line); + REQUIRE(line == ""); + + StreamHandler::skip_to_next_blank_line(stream, line); + REQUIRE(line == ""); + } + } +} + +TEST_CASE("Test the string trim feature", "[string]") { + std::string s = " there might be some white space.... "; + + REQUIRE(String::trim(s) == "there might be some white space...."); +} + +TEST_CASE("Test string split feature", "[string]") { + std::string s = "name1 name2 name3 name4 name5 "; + std::vector vs = {"name1", "name2", "name3", "name4", "name5"}; + + std::vector v; + + String::split(s, v); + + REQUIRE(v == vs); +} + +} // Utils diff --git a/test/testing_utils/common.cpp b/test/testing_utils/common.cpp new file mode 100644 index 0000000..4c3ef54 --- /dev/null +++ b/test/testing_utils/common.cpp @@ -0,0 +1,45 @@ +#include "testing_utils/common.hpp" +#include "testing_utils/defines.hpp" +#include +#include +#include +#include +#include +#include + +// Don't want to use tmpnam due to warnings. So here is an alternative using time and random numbers. +// This should be platform agnostic as well. +static std::string generate_unique_filename(); +std::string generate_unique_filename() { + // Get current time since epoch + auto now = std::chrono::system_clock::now(); + auto duration = now.time_since_epoch(); + auto milliseconds = std::chrono::duration_cast(duration).count(); + + // Generate a random number + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 9999); + int random_num = dis(gen); + + // Concatenate time and random number to create a unique filename + return "temp_file_" + std::to_string(milliseconds) + "_" + std::to_string(random_num) + ".dbc"; +} + +std::string create_temporary_dbc_with(const char* contents) { + std::filesystem::path temp_dir = std::filesystem::temp_directory_path(); + + // Generate a unique temporary file name + std::string filename = generate_unique_filename(); + std::filesystem::path temp_file = temp_dir / filename; + + std::ofstream file(temp_file); + if (!file.is_open()) { + throw std::runtime_error("Failed to create temporary file."); + } + + file << contents << std::endl; + file.close(); + + return temp_file.string(); +} diff --git a/test/testing_utils/common.hpp b/test/testing_utils/common.hpp new file mode 100644 index 0000000..49ee838 --- /dev/null +++ b/test/testing_utils/common.hpp @@ -0,0 +1,8 @@ +#ifndef COMMON_H +#define COMMON_H + +#include + +std::string create_temporary_dbc_with(const char* contents); + +#endif // COMMON_H diff --git a/test/defines.hpp b/test/testing_utils/defines.hpp similarity index 97% rename from test/defines.hpp rename to test/testing_utils/defines.hpp index fcd0206..42d7ee8 100644 --- a/test/defines.hpp +++ b/test/testing_utils/defines.hpp @@ -1,22 +1,22 @@ -#include - -// Correctly formated files -static const std::string COMPLEX_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/Complex.dbc"; -static const std::string SIMPLE_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/Simple.dbc"; - -// Files with Errors -static const std::string MISSING_NEW_SYMBOLS_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/MissingNewSymbols.dbc"; -static const std::string MISSING_VERSION_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/MissingVersion.dbc"; -static const std::string MISSING_BIT_TIMING_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/MissingBitTiming.dbc"; -static const std::string TEXT_FILE = std::string(TESTDBCFILES_PATH) + "/TextFile.txt"; - -static const std::string PRIMITIVE_DBC = - R"(VERSION "1.0.0" - -NS_ : - -BS_: - -BU_: DBG DRIVER IO MOTOR SENSOR - -)"; +#include + +// Correctly formated files +static const std::string COMPLEX_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/Complex.dbc"; +static const std::string SIMPLE_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/Simple.dbc"; + +// Files with Errors +static const std::string MISSING_NEW_SYMBOLS_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/MissingNewSymbols.dbc"; +static const std::string MISSING_VERSION_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/MissingVersion.dbc"; +static const std::string MISSING_BIT_TIMING_DBC_FILE = std::string(TESTDBCFILES_PATH) + "/MissingBitTiming.dbc"; +static const std::string TEXT_FILE = std::string(TESTDBCFILES_PATH) + "/TextFile.txt"; + +static const std::string PRIMITIVE_DBC = + R"(VERSION "1.0.0" + +NS_ : + +BS_: + +BU_: DBG DRIVER IO MOTOR SENSOR + +)"; diff --git a/third_party/bitstream/CMakeLists.txt b/third_party/bitstream/CMakeLists.txt deleted file mode 100644 index 043d10f..0000000 --- a/third_party/bitstream/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 3.16) -# FILE_SET needs cmake 3.23 - -project(bitstream) - -add_library(${PROJECT_NAME} STATIC bitstream.c) - -if (${CMAKE_MINOR_VERSION} GREATER_EQUAL 23) - target_sources(${PROJECT_NAME} INTERFACE FILE_SET HEADERS - TYPE HEADERS - BASE_DIRS ${PROJECT_SOURCE_DIR} - FILES bitstream.h) -else() - target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -endif() diff --git a/third_party/bitstream/Readme.md b/third_party/bitstream/Readme.md deleted file mode 100644 index 3704131..0000000 --- a/third_party/bitstream/Readme.md +++ /dev/null @@ -1,2 +0,0 @@ -Bitstream reader. -Files copied from https://github.com/eerimoq/bitstruct/tree/master/bitstruct diff --git a/third_party/bitstream/bitstream.c b/third_party/bitstream/bitstream.c deleted file mode 100644 index cc68ed8..0000000 --- a/third_party/bitstream/bitstream.c +++ /dev/null @@ -1,608 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2019 Erik Moqvist - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "bitstream.h" - -void bitstream_writer_init(struct bitstream_writer_t *self_p, - uint8_t *buf_p) -{ - self_p->buf_p = buf_p; - self_p->byte_offset = 0; - self_p->bit_offset = 0; -} - -int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p) -{ - return (8 * self_p->byte_offset + self_p->bit_offset); -} - -int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p) -{ - return (self_p->byte_offset + (self_p->bit_offset + 7) / 8); -} - -void bitstream_writer_write_bit(struct bitstream_writer_t *self_p, - int value) -{ - if (self_p->bit_offset == 0) { - self_p->buf_p[self_p->byte_offset] = (value << 7); - self_p->bit_offset = 1; - } else { - self_p->buf_p[self_p->byte_offset] |= (value << (8 - self_p->bit_offset - 1)); - - if (self_p->bit_offset == 7) { - self_p->bit_offset = 0; - self_p->byte_offset++; - } else { - self_p->bit_offset++; - } - } -} - -void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p, - const uint8_t *buf_p, - int length) -{ - int i; - uint8_t *dst_p; - - dst_p = &self_p->buf_p[self_p->byte_offset]; - - if (self_p->bit_offset == 0) { - memcpy(dst_p, buf_p, sizeof(uint8_t) * length); - } else { - for (i = 0; i < length; i++) { - dst_p[i] |= (buf_p[i] >> self_p->bit_offset); - dst_p[i + 1] = (uint8_t)(buf_p[i] << (8 - self_p->bit_offset)); - } - } - - self_p->byte_offset += length; -} - -void bitstream_writer_write_u8(struct bitstream_writer_t *self_p, - uint8_t value) -{ - if (self_p->bit_offset == 0) { - self_p->buf_p[self_p->byte_offset] = value; - } else { - self_p->buf_p[self_p->byte_offset] |= (value >> self_p->bit_offset); - self_p->buf_p[self_p->byte_offset + 1] = - (uint8_t)(value << (8 - self_p->bit_offset)); - } - - self_p->byte_offset++; -} - -void bitstream_writer_write_u16(struct bitstream_writer_t *self_p, - uint16_t value) -{ - if (self_p->bit_offset == 0) { - self_p->buf_p[self_p->byte_offset] = (value >> 8); - } else { - self_p->buf_p[self_p->byte_offset] |= (value >> (8 + self_p->bit_offset)); - self_p->buf_p[self_p->byte_offset + 2] = - (uint8_t)(value << (8 - self_p->bit_offset)); - value >>= self_p->bit_offset; - } - - self_p->buf_p[self_p->byte_offset + 1] = (uint8_t)value; - self_p->byte_offset += 2; -} - -void bitstream_writer_write_u32(struct bitstream_writer_t *self_p, - uint32_t value) -{ - int i; - - if (self_p->bit_offset == 0) { - self_p->buf_p[self_p->byte_offset] = (value >> 24); - } else { - self_p->buf_p[self_p->byte_offset] |= (value >> (24 + self_p->bit_offset)); - self_p->buf_p[self_p->byte_offset + 4] = - (uint8_t)(value << (8 - self_p->bit_offset)); - value >>= self_p->bit_offset; - } - - for (i = 3; i > 0; i--) { - self_p->buf_p[self_p->byte_offset + i] = value; - value >>= 8; - } - - self_p->byte_offset += 4; -} - -void bitstream_writer_write_u64(struct bitstream_writer_t *self_p, - uint64_t value) -{ - int i; - - - if (self_p->bit_offset == 0) { - self_p->buf_p[self_p->byte_offset] = (value >> 56); - } else { - self_p->buf_p[self_p->byte_offset] |= (value >> (56 + self_p->bit_offset)); - self_p->buf_p[self_p->byte_offset + 8] = - (uint8_t)(value << (8 - self_p->bit_offset)); - value >>= self_p->bit_offset; - } - - for (i = 7; i > 0; i--) { - self_p->buf_p[self_p->byte_offset + i] = (uint8_t)value; - value >>= 8; - } - - self_p->byte_offset += 8; -} - -void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p, - uint64_t value, - int number_of_bits) -{ - int i; - int first_byte_bits; - int last_byte_bits; - int full_bytes; - - if (number_of_bits == 0) { - return; - } - - /* Align beginning. */ - first_byte_bits = (8 - self_p->bit_offset); - - if (first_byte_bits != 8) { - if (number_of_bits < first_byte_bits) { - self_p->buf_p[self_p->byte_offset] |= - (uint8_t)(value << (first_byte_bits - number_of_bits)); - self_p->bit_offset += number_of_bits; - } else { - self_p->buf_p[self_p->byte_offset] |= (value >> (number_of_bits - - first_byte_bits)); - self_p->byte_offset++; - self_p->bit_offset = 0; - } - - number_of_bits -= first_byte_bits; - - if (number_of_bits <= 0) { - return; - } - } - - /* Align end. */ - last_byte_bits = (number_of_bits % 8); - full_bytes = (number_of_bits / 8); - - if (last_byte_bits != 0) { - self_p->buf_p[self_p->byte_offset + full_bytes] = - (uint8_t)(value << (8 - last_byte_bits)); - value >>= last_byte_bits; - self_p->bit_offset = last_byte_bits; - } - - /* Copy middle bytes. */ - for (i = full_bytes; i > 0; i--) { - self_p->buf_p[self_p->byte_offset + i - 1] = (uint8_t)value; - value >>= 8; - } - - self_p->byte_offset += full_bytes; -} - -void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p, - int value, - int length) -{ - int rest; - - if (value != 0) { - value = 0xff; - } - - rest = (length % 8); - bitstream_writer_write_u64_bits(self_p, value & ((1 << rest) - 1), rest); - bitstream_writer_write_repeated_u8(self_p, value, length / 8); -} - -void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p, - uint8_t value, - int length) -{ - int i; - - for (i = 0; i < length; i++) { - bitstream_writer_write_u8(self_p, value); - } -} - -void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p, - int value) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - 1); - bitstream_writer_write_bit(self_p, value); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p, - const uint8_t *buf_p, - int length) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - 8 * length); - bitstream_writer_write_bytes(self_p, buf_p, length); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p, - uint8_t value) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - 8); - bitstream_writer_write_u8(self_p, value); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p, - uint16_t value) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - 16); - bitstream_writer_write_u16(self_p, value); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p, - uint32_t value) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - 32); - bitstream_writer_write_u32(self_p, value); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p, - uint64_t value) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - 64); - bitstream_writer_write_u64(self_p, value); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p, - uint64_t value, - int number_of_bits) -{ - struct bitstream_writer_bounds_t bounds; - - bitstream_writer_bounds_save(&bounds, - self_p, - (8 * self_p->byte_offset) + self_p->bit_offset, - number_of_bits); - bitstream_writer_write_u64_bits(self_p, value, number_of_bits); - bitstream_writer_bounds_restore(&bounds); -} - -void bitstream_writer_seek(struct bitstream_writer_t *self_p, - int offset) -{ - offset += ((8 * self_p->byte_offset) + self_p->bit_offset); - self_p->byte_offset = (offset / 8); - self_p->bit_offset = (offset % 8); -} - -void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p, - struct bitstream_writer_t *writer_p, - int bit_offset, - int length) -{ - int number_of_bits; - - self_p->writer_p = writer_p; - number_of_bits = (bit_offset % 8); - - if (number_of_bits == 0) { - self_p->first_byte_offset = -1; - } else { - self_p->first_byte_offset = (bit_offset / 8); - self_p->first_byte = writer_p->buf_p[self_p->first_byte_offset]; - self_p->first_byte &= (0xff00 >> number_of_bits); - } - - number_of_bits = ((bit_offset + length) % 8); - - if (number_of_bits == 0) { - self_p->last_byte_offset = -1; - } else { - self_p->last_byte_offset = ((bit_offset + length) / 8); - self_p->last_byte = writer_p->buf_p[self_p->last_byte_offset]; - self_p->last_byte &= ~(0xff00 >> number_of_bits); - writer_p->buf_p[self_p->last_byte_offset] = 0; - } - - if (self_p->first_byte_offset != -1) { - writer_p->buf_p[self_p->first_byte_offset] = 0; - } -} - -void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p) -{ - if (self_p->first_byte_offset != -1) { - self_p->writer_p->buf_p[self_p->first_byte_offset] |= self_p->first_byte; - } - - if (self_p->last_byte_offset != -1) { - self_p->writer_p->buf_p[self_p->last_byte_offset] |= self_p->last_byte; - } -} - -void bitstream_reader_init(struct bitstream_reader_t *self_p, - const uint8_t *buf_p) -{ - self_p->buf_p = buf_p; - self_p->byte_offset = 0; - self_p->bit_offset = 0; -} - -int bitstream_reader_read_bit(struct bitstream_reader_t *self_p) -{ - int value; - - if (self_p->bit_offset == 0) { - value = (self_p->buf_p[self_p->byte_offset] >> 7); - self_p->bit_offset = 1; - } else { - value = ((self_p->buf_p[self_p->byte_offset] >> (7 - self_p->bit_offset)) & 0x1); - - if (self_p->bit_offset == 7) { - self_p->bit_offset = 0; - self_p->byte_offset++; - } else { - self_p->bit_offset++; - } - } - - return (value); -} - -void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p, - uint8_t *buf_p, - int length) -{ - int i; - const uint8_t *src_p; - - src_p = &self_p->buf_p[self_p->byte_offset]; - - if (self_p->bit_offset == 0) { - memcpy(buf_p, src_p, sizeof(uint8_t) * length); - } else { - for (i = 0; i < length; i++) { - buf_p[i] = (src_p[i] << self_p->bit_offset); - buf_p[i] |= (src_p[i + 1] >> (8 - self_p->bit_offset)); - } - } - - self_p->byte_offset += length; -} - -uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p) -{ - uint8_t value; - - value = (self_p->buf_p[self_p->byte_offset] << self_p->bit_offset); - self_p->byte_offset++; - - if (self_p->bit_offset != 0) { - value |= (self_p->buf_p[self_p->byte_offset] >> (8 - self_p->bit_offset)); - } - - return (value); -} - -uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p) -{ - uint16_t value; - int i; - int offset; - const uint8_t *src_p; - - src_p = &self_p->buf_p[self_p->byte_offset]; - offset = (16 + self_p->bit_offset); - value = 0; - - for (i = 0; i < 2; i++) { - offset -= 8; - value |= ((uint16_t)src_p[i] << offset); - } - - if (offset != 0) { - value |= (src_p[2] >> (8 - offset)); - } - - self_p->byte_offset += 2; - - return (value); -} - -uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p) -{ - uint32_t value; - int i; - int offset; - const uint8_t *src_p; - - src_p = &self_p->buf_p[self_p->byte_offset]; - offset = (32 + self_p->bit_offset); - value = 0; - - for (i = 0; i < 4; i++) { - offset -= 8; - value |= ((uint32_t)src_p[i] << offset); - } - - if (offset != 0) { - value |= (src_p[4] >> (8 - offset)); - } - - self_p->byte_offset += 4; - - return (value); -} - -uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p) -{ - uint64_t value; - int i; - int offset; - const uint8_t *src_p; - - src_p = &self_p->buf_p[self_p->byte_offset]; - offset = (64 + self_p->bit_offset); - value = 0; - - for (i = 0; i < 8; i++) { - offset -= 8; - value |= ((uint64_t)src_p[i] << offset); - } - - if (offset != 0) { - value |= ((uint64_t)src_p[8] >> (8 - offset)); - } - - self_p->byte_offset += 8; - - return (value); -} - -uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p, - int number_of_bits) -{ - uint64_t value; - int i; - int first_byte_bits; - int last_byte_bits; - int full_bytes; - - if (number_of_bits == 0) { - return (0); - } - - /* Align beginning. */ - first_byte_bits = (8 - self_p->bit_offset); - - if (first_byte_bits != 8) { - if (number_of_bits < first_byte_bits) { - value = (self_p->buf_p[self_p->byte_offset] >> (first_byte_bits - - number_of_bits)); - value &= ((1 << number_of_bits) - 1); - self_p->bit_offset += number_of_bits; - } else { - value = self_p->buf_p[self_p->byte_offset]; - value &= ((1 << first_byte_bits) - 1); - self_p->byte_offset++; - self_p->bit_offset = 0; - } - - number_of_bits -= first_byte_bits; - - if (number_of_bits <= 0) { - return (value); - } - } else { - value = 0; - } - - /* Copy middle bytes. */ - full_bytes = (number_of_bits / 8); - - for (i = 0; i < full_bytes; i++) { - value <<= 8; - value |= self_p->buf_p[self_p->byte_offset + i]; - } - - /* Last byte. */ - last_byte_bits = (number_of_bits % 8); - - if (last_byte_bits != 0) { - value <<= last_byte_bits; - value |= (self_p->buf_p[self_p->byte_offset + full_bytes] - >> (8 - last_byte_bits)); - self_p->bit_offset = last_byte_bits; - } - - self_p->byte_offset += full_bytes; - - return (value); -} - -void bitstream_reader_seek(struct bitstream_reader_t *self_p, - int offset) -{ - offset += ((8 * self_p->byte_offset) + self_p->bit_offset); - self_p->byte_offset = (offset / 8); - self_p->bit_offset = (offset % 8); -} - -int bitstream_reader_tell(struct bitstream_reader_t *self_p) -{ - return ((8 * self_p->byte_offset) + self_p->bit_offset); -} - -#ifdef __cplusplus -} -#endif diff --git a/third_party/bitstream/bitstream.h b/third_party/bitstream/bitstream.h deleted file mode 100644 index 9ced7b9..0000000 --- a/third_party/bitstream/bitstream.h +++ /dev/null @@ -1,178 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2019 Erik Moqvist - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#ifndef BITSTREAM_H -#define BITSTREAM_H - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#include - -#define BITSTREAM_VERSION "0.8.0" - -struct bitstream_writer_t { - uint8_t *buf_p; - int byte_offset; - int bit_offset; -}; - -struct bitstream_writer_bounds_t { - struct bitstream_writer_t *writer_p; - int first_byte_offset; - uint8_t first_byte; - int last_byte_offset; - uint8_t last_byte; -}; - -struct bitstream_reader_t { - const uint8_t *buf_p; - int byte_offset; - int bit_offset; -}; - -/* - * The writer. - */ - -void bitstream_writer_init(struct bitstream_writer_t *self_p, - uint8_t *buf_p); - -int bitstream_writer_size_in_bits(struct bitstream_writer_t *self_p); - -int bitstream_writer_size_in_bytes(struct bitstream_writer_t *self_p); - -/* Write bits to the stream. Clears each byte before bits are - written. */ -void bitstream_writer_write_bit(struct bitstream_writer_t *self_p, - int value); - -void bitstream_writer_write_bytes(struct bitstream_writer_t *self_p, - const uint8_t *buf_p, - int length); - -void bitstream_writer_write_u8(struct bitstream_writer_t *self_p, - uint8_t value); - -void bitstream_writer_write_u16(struct bitstream_writer_t *self_p, - uint16_t value); - -void bitstream_writer_write_u32(struct bitstream_writer_t *self_p, - uint32_t value); - -void bitstream_writer_write_u64(struct bitstream_writer_t *self_p, - uint64_t value); - -/* Upper unused bits must be zero. */ -void bitstream_writer_write_u64_bits(struct bitstream_writer_t *self_p, - uint64_t value, - int number_of_bits); - -void bitstream_writer_write_repeated_bit(struct bitstream_writer_t *self_p, - int value, - int length); - -void bitstream_writer_write_repeated_u8(struct bitstream_writer_t *self_p, - uint8_t value, - int length); - -/* Insert bits into the stream. Leaves all other bits unmodified. */ -void bitstream_writer_insert_bit(struct bitstream_writer_t *self_p, - int value); - -void bitstream_writer_insert_bytes(struct bitstream_writer_t *self_p, - const uint8_t *buf_p, - int length); - -void bitstream_writer_insert_u8(struct bitstream_writer_t *self_p, - uint8_t value); - -void bitstream_writer_insert_u16(struct bitstream_writer_t *self_p, - uint16_t value); - -void bitstream_writer_insert_u32(struct bitstream_writer_t *self_p, - uint32_t value); - -void bitstream_writer_insert_u64(struct bitstream_writer_t *self_p, - uint64_t value); - -void bitstream_writer_insert_u64_bits(struct bitstream_writer_t *self_p, - uint64_t value, - int number_of_bits); - -/* Move write position. Seeking backwards makes the written size - smaller. Use write with care after seek, as seek does not clear - bytes. */ -void bitstream_writer_seek(struct bitstream_writer_t *self_p, - int offset); - -/* Save-restore first and last bytes in given range, so write can be - used in given range. */ -void bitstream_writer_bounds_save(struct bitstream_writer_bounds_t *self_p, - struct bitstream_writer_t *writer_p, - int bit_offset, - int length); - -void bitstream_writer_bounds_restore(struct bitstream_writer_bounds_t *self_p); - -/* - * The reader. - */ - -void bitstream_reader_init(struct bitstream_reader_t *self_p, - const uint8_t *buf_p); - -/* Read bits from the stream. */ -int bitstream_reader_read_bit(struct bitstream_reader_t *self_p); - -void bitstream_reader_read_bytes(struct bitstream_reader_t *self_p, - uint8_t *buf_p, - int length); - -uint8_t bitstream_reader_read_u8(struct bitstream_reader_t *self_p); - -uint16_t bitstream_reader_read_u16(struct bitstream_reader_t *self_p); - -uint32_t bitstream_reader_read_u32(struct bitstream_reader_t *self_p); - -uint64_t bitstream_reader_read_u64(struct bitstream_reader_t *self_p); - -uint64_t bitstream_reader_read_u64_bits(struct bitstream_reader_t *self_p, - int number_of_bits); - -/* Move read position. */ -void bitstream_reader_seek(struct bitstream_reader_t *self_p, - int offset); - -/* Get read position. */ -int bitstream_reader_tell(struct bitstream_reader_t *self_p); - -#ifdef __cplusplus -} -#endif // __cplusplus - -#endif // BITSTREAM_H