From 67648ce545637fa537f897bddaae312b9bcfc5e7 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:17:35 +0200 Subject: [PATCH 01/23] Fixed broken const-correctness in dbc.hpp and dbc.cpp. --- include/libdbc/dbc.hpp | 16 ++++++++-------- src/dbc.cpp | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index 5e0e9e4..f884d89 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -27,18 +27,18 @@ class DbcParser : public Parser { 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; + const std::string& get_version() const; + const std::vector& get_nodes() const; + 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; + const std::vector& unused_lines() const; private: - std::string version; - std::vector nodes; - std::vector messages; + std::string version{}; + std::vector nodes{}; + std::vector messages{}; std::regex version_re; std::regex bit_timing_re; @@ -48,7 +48,7 @@ class DbcParser : public Parser { std::regex value_re; std::regex signal_re; - std::vector missed_lines; + std::vector missed_lines{}; void parse_dbc_header(std::istream& file_stream); void parse_dbc_nodes(std::istream& file_stream); diff --git a/src/dbc.cpp b/src/dbc.cpp index 89e8c24..f249e98 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -107,15 +107,15 @@ std::string DbcParser::get_extension(const std::string& file_name) { return ""; } -std::string DbcParser::get_version() const { +const std::string& DbcParser::get_version() const { return version; } -std::vector DbcParser::get_nodes() const { +const std::vector& DbcParser::get_nodes() const { return nodes; } -std::vector DbcParser::get_messages() const { +const std::vector& DbcParser::get_messages() const { return messages; } @@ -246,7 +246,7 @@ void DbcParser::parse_dbc_messages(const std::vector& lines) { } } -std::vector DbcParser::unused_lines() const { +const std::vector& DbcParser::unused_lines() const { return missed_lines; } From 0fd65cd25e08bec8ab85a0cc93bcac20d5bfc1c7 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:23:16 +0200 Subject: [PATCH 02/23] Added more const-correctness fixes. --- include/libdbc/message.hpp | 12 ++++++------ include/libdbc/signal.hpp | 6 +++--- src/message.cpp | 2 +- src/signal.cpp | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/include/libdbc/message.hpp b/include/libdbc/message.hpp index 438dc7f..52822b7 100644 --- a/include/libdbc/message.hpp +++ b/include/libdbc/message.hpp @@ -24,7 +24,7 @@ struct Message { ParseSignalsStatus parse_signals(const std::vector& data, std::vector& values) const; void append_signal(const Signal& signal); - std::vector get_signals() const; + const std::vector& get_signals() const; uint32_t id() const; uint8_t size() const; const std::string& name() const; @@ -33,11 +33,11 @@ struct Message { 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; + 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); }; diff --git a/include/libdbc/signal.hpp b/include/libdbc/signal.hpp index 18ed3da..63d7724 100644 --- a/include/libdbc/signal.hpp +++ b/include/libdbc/signal.hpp @@ -30,7 +30,7 @@ struct Signal { Signal() = delete; virtual ~Signal() = default; - explicit Signal(std::string name, + explicit Signal(const std::string& name, bool is_multiplexed, uint32_t start_bit, uint32_t size, @@ -40,8 +40,8 @@ struct Signal { double offset, double min, double max, - std::string unit, - std::vector receivers); + const std::string& unit, + const std::vector& receivers); virtual bool operator==(const Signal& rhs) const; bool operator<(const Signal& rhs) const; diff --git a/src/message.cpp b/src/message.cpp index ffe4526..268ebd1 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -92,7 +92,7 @@ void Message::append_signal(const Signal& signal) { m_signals.push_back(signal); } -std::vector Message::get_signals() const { +const std::vector& Message::get_signals() const { return m_signals; } diff --git a/src/signal.cpp b/src/signal.cpp index d40bf2f..6b87a53 100644 --- a/src/signal.cpp +++ b/src/signal.cpp @@ -5,7 +5,7 @@ #include namespace Libdbc { -Signal::Signal(std::string name, +Signal::Signal(const std::string& name, bool is_multiplexed, uint32_t start_bit, uint32_t size, @@ -15,8 +15,8 @@ Signal::Signal(std::string name, double offset, double min, double max, - std::string unit, - std::vector receivers) + const std::string& unit, + const std::vector& receivers) : name(name) , is_multiplexed(is_multiplexed) , start_bit(start_bit) From 8c70c6352a4add0dec9728be308235a2df973428 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:39:02 +0200 Subject: [PATCH 03/23] Removed String::trim function, in favour in trim template functions --- src/utils.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/utils.cpp b/src/utils.cpp index eed3134..cabd665 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -65,13 +65,6 @@ std::istream& StreamHandler::skip_to_next_blank_line(std::istream& stream, std:: 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 From 214d6528ab6ac52997d433db5005add1e274a61b Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:39:20 +0200 Subject: [PATCH 04/23] Fixed broken logic in get_line function. --- src/utils.cpp | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/utils.cpp b/src/utils.cpp index cabd665..9adc1c0 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -8,21 +8,11 @@ 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::getline(stream, line); + if (!line.empty() && line.back() == '\r') { + line.pop_back(); // strip CR from CRLF + } + return stream; } std::istream& StreamHandler::get_next_non_blank_line(std::istream& stream, std::string& line) { From 3d6e7106dda7f2746195445c698de59087a5031e Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:40:06 +0200 Subject: [PATCH 05/23] Replaced String::trim function with generic trim functions which work with modern C++ types, such as `string_view`. --- include/libdbc/utils/utils.hpp | 42 +++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index fd2fba2..ea97e78 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -6,6 +6,10 @@ #include #include +#if __cplusplus >= 201703L +# include +#endif // __cplusplus >= 201703L + namespace Utils { class StreamHandler { @@ -26,9 +30,45 @@ class StreamHandler { static std::istream& skip_to_next_blank_line(std::istream& stream, std::string& line); }; +#if __cplusplus >= 201703 +template +constexpr auto trim(const T& value) { +#else +template +std::string trim(const T& value) { +#endif // #if __cplusplus >= 201703 + T trimmedValue = value; + trimmedValue.erase(trimmedValue.begin(), std::find_if(trimmedValue.begin(), trimmedValue.end(), [](int ch) { return !std::isspace(ch); })); + trimmedValue.erase(std::find_if(trimmedValue.rbegin(), trimmedValue.rend(), [](int ch) { return !std::isspace(ch); }).base(), trimmedValue.end()); + return trimmedValue; +} + +#if __cplusplus >= 201703L +template +constexpr auto trim(const T& value, const std::string_view& trimChars) { + T trimmedValue = value; + trimmedValue.erase(trimmedValue.begin(), std::find_if(trimmedValue.begin(), trimmedValue.end(), [&trimChars](int ch) { return trimChars.find(static_cast(ch)) == std::string_view::npos; })); + trimmedValue.erase(std::find_if(trimmedValue.rbegin(), trimmedValue.rend(), [&trimChars](int ch) { return trimChars.find(static_cast(ch)) == std::string_view::npos; }).base(), trimmedValue.end()); + return trimmedValue; +} +#endif // __cplusplus >= 201703L + +#if __cplusplus >= 201703 +template +constexpr auto trim(const T& value, std::initializer_list& trimChars) { +#else +template +std::string trim(const T& value, std::initializer_list& trimChars) { +#endif // __cplusplus >= 201703 + T trimmedValue = value; + trimmedValue.erase(trimmedValue.begin(), std::find_if(trimmedValue.begin(), trimmedValue.end(), [&trimChars](int ch) { return std::find(trimChars.begin(), trimChars.end(), static_cast(ch)) == trimChars.end(); })); + trimmedValue.erase(std::find_if(trimmedValue.rbegin(), trimmedValue.rend(), [&trimChars](int ch) { return std::find(trimChars.begin(), trimChars.end(), static_cast(ch)) == trimChars.end(); }).base(), trimmedValue.end()); + return trimmedValue; +} + class String { public: - static std::string trim(const std::string& line); + static std::string trim(const std::string& line) { return Utils::trim(line); } template static void split(const std::string& str, Container& cont, char delim = ' ') { From 3abbf0c7634c1984f590899bae930dc4b1e4c494 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:48:20 +0200 Subject: [PATCH 06/23] Test directly uses trim template --- test/test_utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_utils.cpp b/test/test_utils.cpp index af4929c..2441d66 100644 --- a/test/test_utils.cpp +++ b/test/test_utils.cpp @@ -78,7 +78,7 @@ end"; 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...."); + REQUIRE(trim(s) == "there might be some white space...."); } TEST_CASE("Test string split feature", "[string]") { From 01747c7e0d541b5c16d4e333a5823d04dde7c55a Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 15:48:51 +0200 Subject: [PATCH 07/23] Added header. Added support for concepts, if enabled and supported by the compiler. --- include/libdbc/utils/utils.hpp | 38 +++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index ea97e78..f39c50c 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -2,6 +2,7 @@ #ifndef UTILS_HPP #define UTILS_HPP +#include #include #include #include @@ -10,6 +11,12 @@ # include #endif // __cplusplus >= 201703L +#if __cpp_concepts >= 201907 +#include +template +concept procsys_stringable = requires(Str s) { { s.data() + s.size() } -> std::convertible_to; }; +#endif // __cpp_concepts >= 201907 + namespace Utils { class StreamHandler { @@ -31,7 +38,11 @@ class StreamHandler { }; #if __cplusplus >= 201703 -template +# if __cpp_concepts >= 201907 + template +# else + template +# endif // __cpp_concepts >= 201907 constexpr auto trim(const T& value) { #else template @@ -43,18 +54,12 @@ std::string trim(const T& value) { return trimmedValue; } -#if __cplusplus >= 201703L -template -constexpr auto trim(const T& value, const std::string_view& trimChars) { - T trimmedValue = value; - trimmedValue.erase(trimmedValue.begin(), std::find_if(trimmedValue.begin(), trimmedValue.end(), [&trimChars](int ch) { return trimChars.find(static_cast(ch)) == std::string_view::npos; })); - trimmedValue.erase(std::find_if(trimmedValue.rbegin(), trimmedValue.rend(), [&trimChars](int ch) { return trimChars.find(static_cast(ch)) == std::string_view::npos; }).base(), trimmedValue.end()); - return trimmedValue; -} -#endif // __cplusplus >= 201703L - #if __cplusplus >= 201703 -template +# if __cpp_concepts >= 201907 + template +# else + template +# endif // __cpp_concepts >= 201907 constexpr auto trim(const T& value, std::initializer_list& trimChars) { #else template @@ -66,6 +71,15 @@ std::string trim(const T& value, std::initializer_list& trimChars) { return trimmedValue; } +#if __cplusplus >= 201703L +# if __cpp_concepts >= 201907 + template +# else + template +# endif // __cpp_concepts >= 201907 +constexpr auto trim(const T& value, const std::string_view& trimChars) { return trim(value, std::initializer_list(trimChars.begin(), trimChars.end())); } +#endif // __cplusplus >= 201703L + class String { public: static std::string trim(const std::string& line) { return Utils::trim(line); } From 4b470f1f58bc5ae571eb6762d615efcdf0e222ec Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:15:22 +0200 Subject: [PATCH 08/23] Logic error in unit test --- test/test_utils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_utils.cpp b/test/test_utils.cpp index 2441d66..c706c8c 100644 --- a/test/test_utils.cpp +++ b/test/test_utils.cpp @@ -41,7 +41,8 @@ maybe not this one either\n\ \n\ Someone wrote something....\n\ b\n\ -end"; +end\ +"; std::istringstream stream(test_string); From ab6fae9b240b3a06fd0b035386d723b044c495bb Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:15:44 +0200 Subject: [PATCH 09/23] Added isWhitespaceOrEmpty template function --- include/libdbc/utils/utils.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index f39c50c..5514b75 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -63,7 +63,7 @@ std::string trim(const T& value) { constexpr auto trim(const T& value, std::initializer_list& trimChars) { #else template -std::string trim(const T& value, std::initializer_list& trimChars) { +std::string trim(const T& value, const std::initializer_list& trimChars) { #endif // __cplusplus >= 201703 T trimmedValue = value; trimmedValue.erase(trimmedValue.begin(), std::find_if(trimmedValue.begin(), trimmedValue.end(), [&trimChars](int ch) { return std::find(trimChars.begin(), trimChars.end(), static_cast(ch)) == trimChars.end(); })); @@ -80,6 +80,21 @@ std::string trim(const T& value, std::initializer_list& trimChars) { constexpr auto trim(const T& value, const std::string_view& trimChars) { return trim(value, std::initializer_list(trimChars.begin(), trimChars.end())); } #endif // __cplusplus >= 201703L +/** + * @brief Checks if the given string is empty or contains only whitespace characters. + * + * @tparam T The typeparam; must be stringable. + * + * @param str The string to check against. + * + * @return true If the string is empty or consists only of whitespace. + * @return false Otherwise. + */ +template +constexpr bool isWhitespaceOrEmpty(const T& str) { + return std::all_of(str.begin(), str.end(), [](char c) { return std::isspace(c); }); +} + class String { public: static std::string trim(const std::string& line) { return Utils::trim(line); } From 2094309a7bb9b381d7af0b98a6735f7025178df2 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:16:44 +0200 Subject: [PATCH 10/23] Fixed weird logic in `get_next_non_blank_line` and `skip_to_next_blank_line`. Removed expensive regex in exchange for simpler and faster char checks. --- src/utils.cpp | 52 ++++++++++++++++++++++++--------------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/utils.cpp b/src/utils.cpp index 9adc1c0..01d2838 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -16,43 +16,39 @@ std::istream& StreamHandler::get_line(std::istream& stream, std::string& line) { } 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; + // NOTE: + // The test expects this function to return a blank string if it has reached the end. + // IMO this behaviour is incorrect and it should always return the last valid string. + // - S. Cahill. - while (is_blank) { - Utils::StreamHandler::get_line(stream, line); + for (;;) { + Utils::StreamHandler::get_line(stream, line); // must strip trailing '\r' - std::regex_search(line, match, whitespace_re); + if (!stream) { // EOF or error after attempt to read + line.clear(); // test expects "" at/after EOF; remove me if the behaviour is to be fixed. + return stream; + } - if ((!line.empty() && !match.empty()) || (stream.eof())) { - if ((match.length(1) > 0) || (stream.eof())) { - is_blank = false; - } - } - } - - return stream; + if (!Utils::isWhitespaceOrEmpty(line)) { + return stream; // found a non-blank line + } + } } 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; + for (;;) { + Utils::StreamHandler::get_line(stream, line); // must strip trailing '\r' - while (!line_is_empty) { - Utils::StreamHandler::get_line(stream, line); + if (!stream) { // EOF or error after attempt to read + line.clear(); // test expects "" at/after EOF; remove me if the behaviour is to be fixed. + return stream; + } - std::regex_search(line, match, whitespace_re); - - if ((match.length(1) == 0) || (stream.eof())) { - line_is_empty = true; - } - } - - return stream; + if (Utils::isWhitespaceOrEmpty(line)) { + return stream; // found a blank line + } + } } double String::convert_to_double(const std::string& value, double default_value) { From 8f6f31bf2a253686a997c15aac0be4e66b281218 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:29:10 +0200 Subject: [PATCH 11/23] Added copyright notices and documentation to added functions --- include/libdbc/utils/utils.hpp | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index 5514b75..5d3d65b 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -37,6 +37,15 @@ class StreamHandler { static std::istream& skip_to_next_blank_line(std::istream& stream, std::string& line); }; +/** + * @brief Trims the specified characters from the beginning and end of the given string. + * + * @tparam T The typeparam; must be stringable. + * @param value The string to trim. + * @return A new string with the specified characters trimmed. + * + * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) + */ #if __cplusplus >= 201703 # if __cpp_concepts >= 201907 template @@ -54,6 +63,16 @@ std::string trim(const T& value) { return trimmedValue; } +/** + * @brief Trims the specified characters from the beginning and end of the given string. + * + * @tparam T The typeparam; must be stringable. + * @param value The string to trim. + * @param trimChars The characters to trim from the string. + * @return A new string with the specified characters trimmed. + * + * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) + */ #if __cplusplus >= 201703 # if __cpp_concepts >= 201907 template @@ -71,6 +90,16 @@ std::string trim(const T& value, const std::initializer_list& trimChars) return trimmedValue; } +/** + * @brief Trims the specified characters from the beginning and end of the given string. + * + * @tparam T The typeparam; must be stringable. + * @param value The string to trim. + * @param trimChars The characters to trim from the string. + * @return A new string with the specified characters trimmed. + * + * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) + */ #if __cplusplus >= 201703L # if __cpp_concepts >= 201907 template @@ -89,6 +118,8 @@ constexpr auto trim(const T& value, const std::string_view& trimChars) { return * * @return true If the string is empty or consists only of whitespace. * @return false Otherwise. + * + * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) */ template constexpr bool isWhitespaceOrEmpty(const T& str) { From 61f6130f1f68968d87288b09fbc0ceb0e2bc079d Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:39:20 +0200 Subject: [PATCH 12/23] More const correctness fixes. --- include/libdbc/dbc.hpp | 2 +- src/dbc.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index f884d89..fdcea96 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -31,7 +31,7 @@ class DbcParser : public Parser { const std::vector& get_nodes() const; const std::vector& get_messages() const; - Message::ParseSignalsStatus parse_message(uint32_t message_id, const std::vector& data, std::vector& out_values); + Message::ParseSignalsStatus parse_message(uint32_t message_id, const std::vector& data, std::vector& out_values) const; const std::vector& unused_lines() const; diff --git a/src/dbc.cpp b/src/dbc.cpp index f249e98..d93c3a2 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -119,7 +119,7 @@ const 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) { +Message::ParseSignalsStatus DbcParser::parse_message(const uint32_t message_id, const std::vector& data, std::vector& out_values) const { for (const auto& message : messages) { if (message.id() == message_id) { return message.parse_signals(data, out_values); From 32f2dbdf549a25ea9ddbc70a9223b6eec01a9f7e Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:40:23 +0200 Subject: [PATCH 13/23] Removed extra `.c_str()` call. --- src/dbc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dbc.cpp b/src/dbc.cpp index d93c3a2..f2d39fd 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -93,7 +93,7 @@ void DbcParser::parse_file(const std::string& file_name) { throw NonDbcFileFormatError(file_name, extension); } - std::ifstream stream(file_name.c_str()); + std::ifstream stream(file_name); parse_file(stream); } From e281cc870fc0c8ab4f04d228dcd84057d4448984 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 16:54:51 +0200 Subject: [PATCH 14/23] Added functions to determine whether or not a .dbc file is valid or not. The file extension is not a definitive answer to anything. Shoving a JPEG image with a modified extension would falsely identify that file as a valid DBC. --- include/libdbc/dbc.hpp | 11 +++++++++++ src/dbc.cpp | 31 ++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index fdcea96..b2a8b41 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -8,6 +8,10 @@ #include #include +#if __cplusplus >= 201703L + #include +#endif // __cplusplus >= 201703L + namespace Libdbc { class Parser { @@ -35,6 +39,13 @@ class DbcParser : public Parser { const std::vector& unused_lines() const; + #if __cplusplus >= 201703L + static bool is_valid_dbc_file(const std::filesystem::path&); + #else + static bool is_valid_dbc_file(const std::string&); + #endif // __cplusplus >= 201703L + static bool is_valid_dbc_file(std::istream& stream); + private: std::string version{}; std::vector nodes{}; diff --git a/src/dbc.cpp b/src/dbc.cpp index f2d39fd..13aed9c 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -70,6 +70,32 @@ DbcParser::DbcParser() + whiteSpace + receiverPattern) { } +#if __cplusplus >= 201703L +bool DbcParser::is_valid_dbc_file(const std::filesystem::path& file_path) { +#else +bool DbcParser::is_valid_dbc_file(const std::string& file_path) { +#endif // __cplusplus >= 201703L + + std::ifstream testStream{file_path}; + if (!testStream.is_open()) { return false; } + + return is_valid_dbc_file(testStream); +} + +bool DbcParser::is_valid_dbc_file(std::istream& stream) { + stream.seekg(0, std::ios::beg); + + try { + DbcParser parser{}; + parser.parse_dbc_header(stream); + } catch (const Exception& e) { + // If the file is missing the version, it is not a valid DBC file + return false; + } + + return true; +} + void DbcParser::parse_file(std::istream& stream) { std::string line; std::vector lines; @@ -88,9 +114,8 @@ void DbcParser::parse_file(std::istream& stream) { } void DbcParser::parse_file(const std::string& file_name) { - auto extension = get_extension(file_name); - if (extension != ".dbc") { - throw NonDbcFileFormatError(file_name, extension); + if (!is_valid_dbc_file(file_name)) { + throw NonDbcFileFormatError(file_name, get_extension(file_name)); } std::ifstream stream(file_name); From 4d237746067ac9ed22c8cc24809f6f20996a9c08 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 17:01:38 +0200 Subject: [PATCH 15/23] Fixed bug reported in "parseSignals should fail if frame data length is too small" Fixes #29 --- include/libdbc/message.hpp | 1 + src/message.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/include/libdbc/message.hpp b/include/libdbc/message.hpp index 52822b7..f624fa1 100644 --- a/include/libdbc/message.hpp +++ b/include/libdbc/message.hpp @@ -19,6 +19,7 @@ struct Message { ErrorBigEndian, ErrorUnknownID, ErrorInvalidConversion, + ErrorInvalidSignalSize, }; ParseSignalsStatus parse_signals(const std::vector& data, std::vector& values) const; diff --git a/src/message.cpp b/src/message.cpp index 268ebd1..ab7cd7b 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,3 +1,5 @@ +#include + #include #include #include @@ -35,15 +37,20 @@ Message::ParseSignalsStatus Message::parse_signals(const std::vector& d 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]; } + data_little_endian = ntohl(data_little_endian); // 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.size > len) { + return ParseSignalsStatus::ErrorInvalidSignalSize; + } + 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; From 13ebc8b2a791352896d106548fae5486b9592b61 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 23:31:34 +0200 Subject: [PATCH 16/23] Clang doesn't like ntohl (I misread it as being 64 bit); swapped to generic template --- include/libdbc/utils/utils.hpp | 24 ++++++++++++++++++++++++ src/message.cpp | 6 +++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index 5d3d65b..16b53d5 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -126,6 +126,30 @@ constexpr bool isWhitespaceOrEmpty(const T& str) { return std::all_of(str.begin(), str.end(), [](char c) { return std::isspace(c); }); } +/** + * @brief Swaps the endianness of a multi-byte value. + * + * @tparam T The type of the value. + * @param value The value to swap. + * @return T The value with swapped endianness. + * + * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) + */ +template +T swapEndianness(T value) { + T swappedBytes; + + char* valuePtr = reinterpret_cast(&value); + char* swappedPtr = reinterpret_cast(&swappedBytes); + + auto sizeInBytes = sizeof(T); + for (size_t i = 0; i < sizeInBytes; i++) { + swappedPtr[sizeInBytes - 1 - i] = valuePtr[i]; + } + + return swappedBytes; +} + class String { public: static std::string trim(const std::string& line) { return Utils::trim(line); } diff --git a/src/message.cpp b/src/message.cpp index ab7cd7b..ecc7ff8 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -1,5 +1,3 @@ -#include - #include #include #include @@ -8,6 +6,8 @@ #include #include +#include + namespace Libdbc { constexpr unsigned ONE_BYTE = 8; @@ -39,7 +39,7 @@ Message::ParseSignalsStatus Message::parse_signals(const std::vector& d for (std::size_t i = 0; i < size; i++) { data_big_endian = (data_big_endian << ONE_BYTE) | (uint64_t)data[i]; } - data_little_endian = ntohl(data_little_endian); + data_little_endian = Utils::swapEndianness(data_little_endian); // TODO: does this also work on a big endian machine? From 601ef66c8f45383aaa17d67651a16850392dd34c Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 23:37:19 +0200 Subject: [PATCH 17/23] Kicked out string_view overload --- include/libdbc/utils/utils.hpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index 16b53d5..69654a3 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -90,25 +90,6 @@ std::string trim(const T& value, const std::initializer_list& trimChars) return trimmedValue; } -/** - * @brief Trims the specified characters from the beginning and end of the given string. - * - * @tparam T The typeparam; must be stringable. - * @param value The string to trim. - * @param trimChars The characters to trim from the string. - * @return A new string with the specified characters trimmed. - * - * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) - */ -#if __cplusplus >= 201703L -# if __cpp_concepts >= 201907 - template -# else - template -# endif // __cpp_concepts >= 201907 -constexpr auto trim(const T& value, const std::string_view& trimChars) { return trim(value, std::initializer_list(trimChars.begin(), trimChars.end())); } -#endif // __cplusplus >= 201703L - /** * @brief Checks if the given string is empty or contains only whitespace characters. * From 3f0ec28537d92e9667e426ed2ab71e58301f8344 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Fri, 15 Aug 2025 23:45:05 +0200 Subject: [PATCH 18/23] Reverted endian little change; somehow there's a difference between how the value was originally built and swapping the bytes --- src/message.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/message.cpp b/src/message.cpp index ecc7ff8..7610d57 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -37,9 +37,9 @@ Message::ParseSignalsStatus Message::parse_signals(const std::vector& d 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]; } - data_little_endian = Utils::swapEndianness(data_little_endian); // TODO: does this also work on a big endian machine? @@ -47,10 +47,6 @@ Message::ParseSignalsStatus Message::parse_signals(const std::vector& d uint64_t value = 0; for (const auto& signal : m_signals) { - if (signal.size > len) { - return ParseSignalsStatus::ErrorInvalidSignalSize; - } - 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; From 2afd3a1efa6d547de48fb4e0876351dbcc0fd5cd Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Tue, 19 Aug 2025 23:13:51 +0200 Subject: [PATCH 19/23] Added option to enable modern C++ versions. --- CMakeLists.txt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 551af92..767a2b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ project(dbc VERSION 0.5.0 DESCRIPTION "C++ DBC Parser") # -- PROJECT OPTIONS -- # option(DBC_ENABLE_TESTS "Enable Unittests" ON) +option(DBC_MODERN_CPP "Enable support for modern C++" OFF) 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) @@ -21,8 +22,13 @@ 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) +if (DBC_ENABLE_TESTS OR DBC_MODERN_CPP) + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED True) +else() + set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD_REQUIRED True) +endif() find_package(FastFloat QUIET) if (NOT ${FastFloat_FOUND}) From a3e3323f04c8e6871676fe893f94da1df3497eb9 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Tue, 19 Aug 2025 23:14:13 +0200 Subject: [PATCH 20/23] Refactored code; fixed tests. --- include/libdbc/dbc.hpp | 6 ++--- include/libdbc/exceptions/error.hpp | 38 +++++++++-------------------- src/dbc.cpp | 24 ++++++------------ test/test_dbc.cpp | 3 +-- 4 files changed, 23 insertions(+), 48 deletions(-) diff --git a/include/libdbc/dbc.hpp b/include/libdbc/dbc.hpp index b2a8b41..9141133 100644 --- a/include/libdbc/dbc.hpp +++ b/include/libdbc/dbc.hpp @@ -40,11 +40,11 @@ class DbcParser : public Parser { const std::vector& unused_lines() const; #if __cplusplus >= 201703L - static bool is_valid_dbc_file(const std::filesystem::path&); + static void validate_dbc_file(const std::filesystem::path&); #else - static bool is_valid_dbc_file(const std::string&); + static void validate_dbc_file(const std::string&); #endif // __cplusplus >= 201703L - static bool is_valid_dbc_file(std::istream& stream); + static void validate_dbc_file(std::istream& stream); private: std::string version{}; diff --git a/include/libdbc/exceptions/error.hpp b/include/libdbc/exceptions/error.hpp index 03fd5a0..735849d 100644 --- a/include/libdbc/exceptions/error.hpp +++ b/include/libdbc/exceptions/error.hpp @@ -22,10 +22,12 @@ class ValidityError : public Exception { 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 - + ")."}; - } + explicit 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 + ")." + ) { } + explicit NonDbcFileFormatError(const std::string& error): + error_msg("File is not of DBC format. Cannot read this type of file (" + error + ").") { } const char* what() const throw() override { return error_msg.c_str(); @@ -35,32 +37,16 @@ class NonDbcFileFormatError : public ValidityError { std::string error_msg; }; -class DbcFileIsMissingVersion : public ValidityError { +class DbcFileIsMissingVersion : public NonDbcFileFormatError { 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; + explicit DbcFileIsMissingVersion(const std::string& line): + NonDbcFileFormatError("Invalid dbc file. Missing the required version header. Attempting to read line: (" + line + ").") { } }; -class DbcFileIsMissingBitTiming : public ValidityError { +class DbcFileIsMissingBitTiming : public NonDbcFileFormatError { 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; + explicit DbcFileIsMissingBitTiming(const std::string& line): + NonDbcFileFormatError("Invalid dbc file. Missing required bit timing in the header. Attempting to read line: (" + line + ").") { } }; } // libdbc diff --git a/src/dbc.cpp b/src/dbc.cpp index 13aed9c..6f1af2b 100644 --- a/src/dbc.cpp +++ b/src/dbc.cpp @@ -71,29 +71,21 @@ DbcParser::DbcParser() } #if __cplusplus >= 201703L -bool DbcParser::is_valid_dbc_file(const std::filesystem::path& file_path) { +void DbcParser::validate_dbc_file(const std::filesystem::path& file_path) { #else -bool DbcParser::is_valid_dbc_file(const std::string& file_path) { +void DbcParser::validate_dbc_file(const std::string& file_path) { #endif // __cplusplus >= 201703L std::ifstream testStream{file_path}; - if (!testStream.is_open()) { return false; } - return is_valid_dbc_file(testStream); + return validate_dbc_file(testStream); } -bool DbcParser::is_valid_dbc_file(std::istream& stream) { +void DbcParser::validate_dbc_file(std::istream& stream) { stream.seekg(0, std::ios::beg); - try { - DbcParser parser{}; - parser.parse_dbc_header(stream); - } catch (const Exception& e) { - // If the file is missing the version, it is not a valid DBC file - return false; - } - - return true; + DbcParser parser{}; + parser.parse_dbc_header(stream); } void DbcParser::parse_file(std::istream& stream) { @@ -114,9 +106,7 @@ void DbcParser::parse_file(std::istream& stream) { } void DbcParser::parse_file(const std::string& file_name) { - if (!is_valid_dbc_file(file_name)) { - throw NonDbcFileFormatError(file_name, get_extension(file_name)); - } + validate_dbc_file(file_name); std::ifstream stream(file_name); diff --git a/test/test_dbc.cpp b/test/test_dbc.cpp index 8ae1b54..59f66c2 100644 --- a/test/test_dbc.cpp +++ b/test/test_dbc.cpp @@ -14,8 +14,7 @@ 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")); + REQUIRE_THROWS_AS(parser->validate_dbc_file(TEXT_FILE), Libdbc::NonDbcFileFormatError); } SECTION("Loading a dbc with missing version header throws an error (VERSION)", "[error]") { From fa8ffb9eaea284cc2a70a2d15243c0d4463da3a5 Mon Sep 17 00:00:00 2001 From: SimonC Date: Thu, 11 Sep 2025 10:49:12 +0200 Subject: [PATCH 21/23] Update CMakeLists.txt Co-authored-by: Devon Adair --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 767a2b0..0ae43ee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(dbc VERSION 0.5.0 DESCRIPTION "C++ DBC Parser") # -- PROJECT OPTIONS -- # option(DBC_ENABLE_TESTS "Enable Unittests" ON) -option(DBC_MODERN_CPP "Enable support for modern C++" OFF) +option(DBC_MODERN_CPP "Enable support for modern C++ 17. Default is C++ 11 for legacy systems." OFF) 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) From d30508f2c2a090080f9b54803234b0fc3a392bc7 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Thu, 11 Sep 2025 10:53:46 +0200 Subject: [PATCH 22/23] Removed copyright --- include/libdbc/utils/utils.hpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/libdbc/utils/utils.hpp b/include/libdbc/utils/utils.hpp index 69654a3..5c4bb56 100644 --- a/include/libdbc/utils/utils.hpp +++ b/include/libdbc/utils/utils.hpp @@ -43,8 +43,6 @@ class StreamHandler { * @tparam T The typeparam; must be stringable. * @param value The string to trim. * @return A new string with the specified characters trimmed. - * - * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) */ #if __cplusplus >= 201703 # if __cpp_concepts >= 201907 @@ -70,8 +68,6 @@ std::string trim(const T& value) { * @param value The string to trim. * @param trimChars The characters to trim from the string. * @return A new string with the specified characters trimmed. - * - * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) */ #if __cplusplus >= 201703 # if __cpp_concepts >= 201907 @@ -99,8 +95,6 @@ std::string trim(const T& value, const std::initializer_list& trimChars) * * @return true If the string is empty or consists only of whitespace. * @return false Otherwise. - * - * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) */ template constexpr bool isWhitespaceOrEmpty(const T& str) { @@ -113,8 +107,6 @@ constexpr bool isWhitespaceOrEmpty(const T& str) { * @tparam T The type of the value. * @param value The value to swap. * @return T The value with swapped endianness. - * - * @copyright 2025 Procyon Systems Inh. Simon Cahill (s.cahill@procyon-systems.de) */ template T swapEndianness(T value) { From 22a64120a08134e89bea69ba7010547c5cc972c7 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Thu, 11 Sep 2025 11:01:30 +0200 Subject: [PATCH 23/23] Updated implementation of to not return blank line, but last known good line --- src/utils.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/utils.cpp b/src/utils.cpp index 01d2838..1670ff9 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -16,17 +16,10 @@ std::istream& StreamHandler::get_line(std::istream& stream, std::string& line) { } std::istream& StreamHandler::get_next_non_blank_line(std::istream& stream, std::string& line) { - - // NOTE: - // The test expects this function to return a blank string if it has reached the end. - // IMO this behaviour is incorrect and it should always return the last valid string. - // - S. Cahill. - for (;;) { Utils::StreamHandler::get_line(stream, line); // must strip trailing '\r' if (!stream) { // EOF or error after attempt to read - line.clear(); // test expects "" at/after EOF; remove me if the behaviour is to be fixed. return stream; }