From 621383ebefab3c154c05778315acc7e781924fdb Mon Sep 17 00:00:00 2001 From: ErniBrown Date: Wed, 21 Feb 2024 18:12:02 +0100 Subject: [PATCH 01/29] Cmake (#13) * Using cmake target export * CPack configuration * Updated doxygen-awesome-css to v2.3.1 * Fixed wrong project title. Whoops. * Removed "lib" prefix from project name, as CMake adds this automatically * Updated maintainer information * Updated README * Added lib prefix to cpack package name * Updated gitignore * Added new "docs" target for HTML Doxygen generation * Updated CPack config to generate .debs, .rpms, etc. * Added new workflow --------- Co-authored-by: Simon Cahill --- .github/workflows/cpack.yml | 48 +++++++++++++++++++++++++++++ .gitignore | 10 ++++++ CMakeLists.txt | 56 ++++++++++++++++++++++++---------- Doxyfile | 2 +- README.md | 4 +-- cmake/cpack.cmake | 43 ++++++++++++++++++++++++++ cmake/libsockcanppConfig.cmake | 3 ++ doxygen-awesome-css | 2 +- include/CMakeLists.txt | 10 ++++++ src/CMakeLists.txt | 4 +++ 10 files changed, 162 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/cpack.yml create mode 100644 cmake/cpack.cmake create mode 100644 cmake/libsockcanppConfig.cmake create mode 100644 include/CMakeLists.txt create mode 100644 src/CMakeLists.txt diff --git a/.github/workflows/cpack.yml b/.github/workflows/cpack.yml new file mode 100644 index 0000000..5c7676d --- /dev/null +++ b/.github/workflows/cpack.yml @@ -0,0 +1,48 @@ +name: CMake + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Pack + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: cpack . + diff --git a/.gitignore b/.gitignore index b3b2648..fdd0afb 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,16 @@ ### build/ +### +# Documentation (Doxygen) +### +html/ + +### +# cpack packages +### +packages/ + ### # .vscode ### diff --git a/CMakeLists.txt b/CMakeLists.txt index 45af466..fddda53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,30 +1,40 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.00) +project(sockcanpp LANGUAGES CXX VERSION 1.0) -option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" OFF) +option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" ON) set(CMAKE_CXX_STANDARD 11) +include(GNUInstallDirs) -include_directories( - include/ -) +add_library(${PROJECT_NAME}) +set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) +set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") -file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) +add_subdirectory(include) +add_subdirectory(src) -add_library(${PROJECT_NAME} ${FILES}) -set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) +install(TARGETS ${PROJECT_NAME} + EXPORT ${PROJECT_NAME}Targets + FILE_SET HEADERS +) -target_include_directories( - ${PROJECT_NAME} +install(EXPORT ${PROJECT_NAME}Targets + FILE ${PROJECT_NAME}Targets.cmake + NAMESPACE ${PROJECT_NAME}:: + DESTINATION lib/cmake/${PROJECT_NAME} +) - INTERFACE include/ - INTERFACE include/exceptions +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${PROJECT_NAME}ConfigVersion.cmake" + VERSION ${${PROJECT_NAME}_VERSION} + COMPATIBILITY AnyNewerVersion ) -include(GNUInstallDirs) -install(TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR}) -install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) +install(FILES "cmake/lib${PROJECT_NAME}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/lib${PROJECT_NAME}ConfigVersion.cmake" + DESTINATION lib/cmake/${PROJECT_NAME}) + set(prefix ${CMAKE_INSTALL_PREFIX}) set(exec_prefix ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}) @@ -34,3 +44,17 @@ set(libdir ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}) configure_file(libsockcanpp.pc.in ${CMAKE_BINARY_DIR}/libsockcanpp.pc @ONLY) # Install pkg-config files install(FILES ${CMAKE_BINARY_DIR}/libsockcanpp.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) + +include(cmake/cpack.cmake) + +### +# Docs target +### +add_custom_target("docs" COMMENT "Create Doxygen documentation") +add_custom_command( + TARGET "docs" + POST_BUILD + COMMENT "Generate Doxygen documentation for publication or reading" + COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) \ No newline at end of file diff --git a/Doxyfile b/Doxyfile index e784ae3..6431cae 100644 --- a/Doxyfile +++ b/Doxyfile @@ -4,7 +4,7 @@ # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "Waveshare 8ch Relay Board Example" +PROJECT_NAME = "Libsockcanpp" PROJECT_NUMBER = PROJECT_BRIEF = "A complete C++ wrapper around socketcan." PROJECT_LOGO = diff --git a/README.md b/README.md index 24c6f6f..aad9aea 100644 --- a/README.md +++ b/README.md @@ -15,14 +15,14 @@ libsockcanpp is a socketcan wrapper library for C++, aimed to be easy to use wit libsockcanpp was designed with use in CMake projects, but it can also easily be integrated into existing Makefile projects, as long as cmake is present on the build system. -1) clone the repository: `git clone https://github.com/Beatsleigher/libsockcanpp.git` +1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 2) create build directory: `mkdir build && cd build` 3) generate build files: `cmake .. -DCMAKE_TOOLCHAIN_FILE=../toolchains/desired-toolchain.cmake` 4) building library: `make -j` ## Incorporating into Cmake projects: -1) clone the repository: `git clone https://github.com/Beatsleigher/libsockcanpp.git` +1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 2) add the following to CMakeLists.txt ```cmake if (NOT TARGET sockcanpp) diff --git a/cmake/cpack.cmake b/cmake/cpack.cmake new file mode 100644 index 0000000..2a6c174 --- /dev/null +++ b/cmake/cpack.cmake @@ -0,0 +1,43 @@ +# these are cache variables, so they could be overwritten with -D, +set(CPACK_PACKAGE_NAME lib${PROJECT_NAME} + CACHE STRING "The resulting package name" +) + +# which is useful in case of packing only selected components instead of the whole thing +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A socketcan wrapper library for C++" + CACHE STRING "Package description for the package metadata" +) + +set(CPACK_VERBATIM_VARIABLES YES) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/packages") + +# https://unix.stackexchange.com/a/11552/254512 +#set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/some")#/${CMAKE_PROJECT_VERSION}") + +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) + +set(CPACK_PACKAGE_CONTACT "contact@simonc.eu") +set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Simon Cahill") + +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE") +set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md") + +# package name for deb. If set, then instead of some-application-0.9.2-Linux.deb +# you'll get some-application_0.9.2_amd64.deb (note the underscores too) +set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT) + +# that is if you want every group to have its own package, +# although the same will happen if this is not set (so it defaults to ONE_PER_GROUP) +# and CPACK_DEB_COMPONENT_INSTALL is set to YES +set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP) + +# without this you won't be able to pack only specified component +set(CPACK_DEB_COMPONENT_INSTALL YES) + +set(CPACK_GENERATOR TGZ DEB TZ STGZ RPM ZIP) + +include(CPack) \ No newline at end of file diff --git a/cmake/libsockcanppConfig.cmake b/cmake/libsockcanppConfig.cmake new file mode 100644 index 0000000..dbee823 --- /dev/null +++ b/cmake/libsockcanppConfig.cmake @@ -0,0 +1,3 @@ +# include(CMakeFindDependencyMacro) +# find_dependency(xxx 2.0) +include(${CMAKE_CURRENT_LIST_DIR}/libsockcanppTargets.cmake) \ No newline at end of file diff --git a/doxygen-awesome-css b/doxygen-awesome-css index 9380569..df88fe4 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit 9380569e8aea36374d848c8a43c6f9c6343d9bc0 +Subproject commit df88fe4fdd97714fadfd3ef17de0b4401f804052 diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt new file mode 100644 index 0000000..c98bc0b --- /dev/null +++ b/include/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.23) + +target_sources(${PROJECT_NAME} + PUBLIC FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR} + FILES + CanDriver.hpp + CanId.hpp + CanMessage.hpp +) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..7c45566 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,4 @@ +target_sources(${PROJECT_NAME} + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp +) From f2463f9ac320e457bdb3dd1f406511fb75916f2a Mon Sep 17 00:00:00 2001 From: SimonC Date: Wed, 6 Mar 2024 18:17:55 +0100 Subject: [PATCH 02/29] Fix wrong queue size while reading multiple frames. (#15) * Fixed wrong documentation comments. Made strings ref-to-const. Removed useless const qualifier for return values. Fixed return type mismatches. Added in-class-initialisers. * Static integers are now constexpr * Fixed bug where wrong queue size is returned. waitForMessages() now uses ioctl(FIONREAD) to return the size of the queue. * Fixed missing lib prefix. Thanks to @sikmir! --- CMakeLists.txt | 2 +- include/CanDriver.hpp | 64 ++++++++++++++++++------------------ src/CanDriver.cpp | 76 ++++++++++++++++++++++++++----------------- 3 files changed, 79 insertions(+), 63 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fddda53..c50e97e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ install(EXPORT ${PROJECT_NAME}Targets include(CMakePackageConfigHelpers) write_basic_package_version_file( - "${PROJECT_NAME}ConfigVersion.cmake" + "lib${PROJECT_NAME}ConfigVersion.cmake" VERSION ${${PROJECT_NAME}_VERSION} COMPATIBILITY AnyNewerVersion ) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index f86c7f5..7dc1b5d 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -61,56 +61,56 @@ namespace sockcanpp { */ class CanDriver { public: // +++ Static +++ - static const int32_t CAN_MAX_DATA_LENGTH; ///!< The maximum amount of bytes allowed in a single CAN frame - static const int32_t CAN_SOCK_RAW; ///!< The raw CAN protocol - static const int32_t CAN_SOCK_SEVEN; ///!< A separate CAN protocol, used by certain embedded device OEMs. + static constexpr int32_t CAN_MAX_DATA_LENGTH = 8; //!< The maximum amount of bytes allowed in a single CAN frame + static constexpr int32_t CAN_SOCK_RAW = CAN_RAW; //!< The raw CAN protocol + static constexpr int32_t CAN_SOCK_SEVEN = 7; //!< A separate CAN protocol, used by certain embedded device OEMs. public: // +++ Constructor / Destructor +++ - CanDriver(const string canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); ///!< Constructor - CanDriver(const string canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); - CanDriver() {} - virtual ~CanDriver() { uninitialiseSocketCan(); } ///!< Destructor + CanDriver(const string& canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); //!< Constructor + CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); + CanDriver() = default; + virtual ~CanDriver() { uninitialiseSocketCan(); } //!< Destructor public: // +++ Getter / Setter +++ - CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } ///!< Sets the default sender ID + CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID - const CanId getDefaultSenderId() const { return this->_defaultSenderId; } ///!< Gets the default sender ID + CanId getDefaultSenderId() const { return this->_defaultSenderId; } //!< Gets the default sender ID - const int32_t getFilterMask() const { return this->_canFilterMask; } ///!< Gets the filter mask used by this instance - const int32_t getMessageQueueSize() const { return this->_queueSize; } ///!< Gets the amount of CAN messages found after last calling waitForMessages() - const int32_t getSocketFd() const { return this->_socketFd; } ///!< The socket file descriptor used by this instance. + int32_t getFilterMask() const { return this->_canFilterMask; } //!< Gets the filter mask used by this instance + int32_t getMessageQueueSize() const { return this->_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() + int32_t getSocketFd() const { return this->_socketFd; } //!< The socket file descriptor used by this instance. public: // +++ I/O +++ - virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); ///!< Waits for CAN messages to appear + virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); //!< Waits for CAN messages to appear - virtual CanMessage readMessage(); ///!< Attempts to read a single message from the bus + virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus - virtual int32_t sendMessage(const CanMessage message, bool forceExtended = false); ///!< Attempts to send a single CAN message - virtual int32_t sendMessageQueue(queue messages, - milliseconds delay = milliseconds(20), bool forceExtended = false); ///!< Attempts to send a queue of messages + virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message + virtual ssize_t sendMessageQueue(queue messages, + milliseconds delay = milliseconds(20), bool forceExtended = false); //!< Attempts to send a queue of messages - virtual queue readQueuedMessages(); ///!< Attempts to read all queued messages from the bus + virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus - virtual void setCanFilterMask(const int32_t mask); ///!< Attempts to set a new CAN filter mask to the BIOS + virtual void setCanFilterMask(const int32_t mask); //!< Attempts to set a new CAN filter mask to the BIOS protected: // +++ Socket Management +++ - virtual void initialiseSocketCan(); ///!< Initialises socketcan - virtual void uninitialiseSocketCan(); ///!< Uninitialises socketcan + virtual void initialiseSocketCan(); //!< Initialises socketcan + virtual void uninitialiseSocketCan(); //!< Uninitialises socketcan private: - virtual CanMessage readMessageLock(bool const lock = true); ///!< readMessage deadlock guard - CanId _defaultSenderId; ///!< The ID to send messages with if no other ID was set. + virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard + CanId _defaultSenderId; //!< The ID to send messages with if no other ID was set. - int32_t _canFilterMask; ///!< The bit mask used to filter CAN messages - int32_t _canProtocol; ///!< The protocol used when communicating via CAN - int32_t _socketFd; ///!< The CAN socket file descriptor - int32_t _queueSize; ////!< The size of the message queue read by waitForMessages() + int32_t _canFilterMask; //!< The bit mask used to filter CAN messages + int32_t _canProtocol; //!< The protocol used when communicating via CAN + int32_t _socketFd{-1}; //!< The CAN socket file descriptor + int32_t _queueSize{0}; ///!< The size of the message queue read by waitForMessages() - ///!< Mutex for thread-safety. - mutex _lock; - mutex _lockSend; + //!< Mutex for thread-safety. + mutex _lock{}; + mutex _lockSend{}; - string _canInterface; ///!< The CAN interface used for communication (e.g. can0, can1, ...) + string _canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) }; @@ -130,7 +130,7 @@ namespace sockcanpp { template string formatString(const string& format, Args... args) { using std::unique_ptr; - auto stringSize = snprintf(NULL, 0, format.c_str(), args...) + 1; // +1 for \0 + auto stringSize = snprintf(nullptr, 0, format.c_str(), args...) + 1; // +1 for \0 unique_ptr buffer(new char[stringSize]); snprintf(buffer.get(), stringSize, format.c_str(), args...); diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 3381a1e..990d02b 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -36,6 +36,7 @@ #include #include +#include #include #include #include @@ -66,6 +67,7 @@ namespace sockcanpp { using std::string; using std::strncpy; using std::unique_lock; + using std::unique_ptr; using std::chrono::milliseconds; using std::this_thread::sleep_for; @@ -73,16 +75,13 @@ namespace sockcanpp { // PUBLIC IMPLEMENTATION // ////////////////////////////////////// - const int32_t CanDriver::CAN_MAX_DATA_LENGTH = 8; - const int32_t CanDriver::CAN_SOCK_RAW = CAN_RAW; - const int32_t CanDriver::CAN_SOCK_SEVEN = 7; #pragma region Object Construction - CanDriver::CanDriver(string canInterface, int32_t canProtocol, const CanId defaultSenderId): + CanDriver::CanDriver(const string& canInterface, int32_t canProtocol, const CanId defaultSenderId): CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) {} - CanDriver::CanDriver(const string canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): - _defaultSenderId(defaultSenderId), _canProtocol(canProtocol), _canInterface(canInterface), _canFilterMask(filterMask), _socketFd(-1) { + CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): + _defaultSenderId(defaultSenderId), _canFilterMask(filterMask), _canProtocol(canProtocol), _canInterface(canInterface) { initialiseSocketCan(); } #pragma endregion @@ -108,9 +107,17 @@ namespace sockcanpp { FD_ZERO(&readFileDescriptors); FD_SET(_socketFd, &readFileDescriptors); - _queueSize = select(_socketFd + 1, &readFileDescriptors, 0, 0, &waitTime); + const auto fdsAvailable = select(_socketFd + 1, &readFileDescriptors, nullptr, nullptr, &waitTime); + + int32_t bytesAvailable{0}; + const auto retCode = ioctl(_socketFd, FIONREAD, &bytesAvailable); + if (retCode == 0) { + _queueSize = static_cast(std::ceil(bytesAvailable / sizeof(can_frame))); + } else { + _queueSize = 0; + } - return _queueSize > 0; + return fdsAvailable > 0; } /** @@ -128,17 +135,23 @@ namespace sockcanpp { * @return CanMessage The message read from the bus. */ CanMessage CanDriver::readMessageLock(bool const lock) { - std::unique_ptr> _lockLck{nullptr}; - if (lock) - _lockLck = std::unique_ptr>{new std::unique_lock{_lock}}; - if (0 > _socketFd) - throw InvalidSocketException("Invalid socket!", _socketFd); - int32_t readBytes{0}; - can_frame canFrame; + unique_ptr> locky{nullptr}; + + if (lock) { + locky = unique_ptr>{new unique_lock{_lock}}; + } + + if (0 > _socketFd) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + ssize_t readBytes{0}; + can_frame canFrame{}; + memset(&canFrame, 0, sizeof(can_frame)); + readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); - if (0 > readBytes) - throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); + + if (0 > readBytes) { throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); } + return CanMessage{canFrame}; } @@ -148,14 +161,14 @@ namespace sockcanpp { * @param message The message to be sent. * @param forceExtended Whether or not to force use of an extended ID. * - * @return int32_t The amount of bytes sent on the bus. + * @return ssize_t The amount of bytes sent on the bus. */ - int32_t CanDriver::sendMessage(const CanMessage message, bool forceExtended) { + ssize_t CanDriver::sendMessage(const CanMessage& message, bool forceExtended) { if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } unique_lock locky(_lockSend); - int32_t bytesWritten = 0; + ssize_t bytesWritten = 0; if (message.getFrameData().size() > CAN_MAX_DATA_LENGTH) { throw CanException(formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH), _socketFd); @@ -181,10 +194,10 @@ namespace sockcanpp { * * @return int32_t The total amount of bytes sent. */ - int32_t CanDriver::sendMessageQueue(queue messages, milliseconds delay, bool forceExtended) { + ssize_t CanDriver::sendMessageQueue(queue messages, milliseconds delay, bool forceExtended) { if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } - int32_t totalBytesWritten = 0; + ssize_t totalBytesWritten = 0; while (!messages.empty()) { totalBytesWritten += sendMessage(messages.front(), forceExtended); @@ -200,12 +213,15 @@ namespace sockcanpp { * @return queue A queue containing the messages read from the bus buffer. */ queue CanDriver::readQueuedMessages() { - if (_socketFd < 0) - throw InvalidSocketException("Invalid socket!", _socketFd); - unique_lock locky(_lock); - queue messages; - for (int32_t i = _queueSize; 0 < i; --i) - messages.emplace(readMessageLock(false)); + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + unique_lock locky{_lock}; + queue messages{}; + + for (int32_t i = _queueSize; 0 < i; --i) { + messages.emplace(readMessageLock(false)); + } + return messages; } @@ -248,8 +264,8 @@ namespace sockcanpp { int64_t fdOptions = 0; int32_t tmpReturn; - memset(&address, 0, sizeof(sizeof(struct sockaddr_can))); - memset(&ifaceRequest, 0, sizeof(sizeof(struct ifreq))); + memset(&address, 0, sizeof(struct sockaddr_can)); + memset(&ifaceRequest, 0, sizeof(struct ifreq)); _socketFd = socket(PF_CAN, SOCK_RAW, _canProtocol); From 9d642e27b824275d184c42d36d6964622b4fff37 Mon Sep 17 00:00:00 2001 From: SimonC Date: Mon, 10 Jun 2024 19:06:08 +0200 Subject: [PATCH 03/29] Fixed issues with CanId reporting incorrect CAN IDs. (#17) * Refactored CanId type to be constexpr and utilise templates. Removed unnecessary checks from constructor. Switched to bitmasks instead of std::bitset. Changed type `uint32_t`to `canid_t` where applicable. Added more operator overloads. * Restructured test directory * Updated gitignore * Added GTest unit tests for CanId type. * Bumped up version * Re-added ability to generate static library. * Sources and headers are now added to test libraries if required. * Added runner for tests * Added CMakeLists for test directory. * Updated test generators. * Updated runners. * Added missing gtest dependency * Trying to fix dependencies * Trying to get GTest to install... * Disabled compiler warning because of unknown pragmas * Disabled same compiler warning in unit tests --- .github/workflows/cmake.yml | 10 +- .github/workflows/cpack.yml | 2 +- .github/workflows/gtest.yml | 48 ++++++ CMakeLists.txt | 30 +++- include/CMakeLists.txt | 13 +- include/CanId.hpp | 252 +++++++++++++++--------------- src/CMakeLists.txt | 11 ++ test/CMakeLists.txt | 31 +--- test/app/CMakeLists.txt | 31 ++++ test/{ => app}/src/Main.cpp | 0 test/unit/.gitignore | 1 + test/unit/CMakeLists.txt | 37 +++++ test/unit/src/CanId_Tests.cpp | 278 ++++++++++++++++++++++++++++++++++ test/unit/src/main.cpp | 16 ++ 14 files changed, 587 insertions(+), 173 deletions(-) create mode 100644 .github/workflows/gtest.yml create mode 100644 test/app/CMakeLists.txt rename test/{ => app}/src/Main.cpp (100%) create mode 100644 test/unit/.gitignore create mode 100644 test/unit/CMakeLists.txt create mode 100644 test/unit/src/CanId_Tests.cpp create mode 100644 test/unit/src/main.cpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index ae83e9a..e880adb 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -1,4 +1,4 @@ -name: CMake +name: Build Debug on: push: @@ -8,7 +8,7 @@ on: env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) - BUILD_TYPE: Release + BUILD_TYPE: Debug jobs: build: @@ -40,9 +40,3 @@ jobs: # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - - name: Test - working-directory: ${{github.workspace}}/build - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -C ${{env.BUILD_TYPE}} - diff --git a/.github/workflows/cpack.yml b/.github/workflows/cpack.yml index 5c7676d..40dc4c7 100644 --- a/.github/workflows/cpack.yml +++ b/.github/workflows/cpack.yml @@ -1,4 +1,4 @@ -name: CMake +name: Build and Pack on: push: diff --git a/.github/workflows/gtest.yml b/.github/workflows/gtest.yml new file mode 100644 index 0000000..8e8cc04 --- /dev/null +++ b/.github/workflows/gtest.yml @@ -0,0 +1,48 @@ +name: Build and Test + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt -y install can-utils libsocketcan-dev googletest + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + - uses: MarkusJx/googletest-installer@v1.1 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + diff --git a/CMakeLists.txt b/CMakeLists.txt index c50e97e..cf1471e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,27 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.0) +project(sockcanpp LANGUAGES CXX VERSION 1.1.0) option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" ON) +option(BUILD_TESTS "Build the tests" OFF) set(CMAKE_CXX_STANDARD 11) include(GNUInstallDirs) -add_library(${PROJECT_NAME}) -set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) -set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +if (BUILD_SHARED_LIBS STREQUAL "ON") + add_library(${PROJECT_NAME}) + set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION}) + set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "") +else() + add_library(${PROJECT_NAME} STATIC) +endif() + +### +# If BUILD_TESTS is set to ON, a static test library with the name of the project suffixed with "_test" will be created +### +if(BUILD_TESTS STREQUAL "ON") + add_library(${PROJECT_NAME}_test STATIC) +endif() add_subdirectory(include) add_subdirectory(src) @@ -57,4 +69,12 @@ add_custom_command( COMMENT "Generate Doxygen documentation for publication or reading" COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} -) \ No newline at end of file +) + +### +# If the CMAKE_BUILD_TYPE is set to Debug, enable the tests +### +if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND BUILD_TESTS STREQUAL "ON") + enable_testing() + add_subdirectory(test) +endif() \ No newline at end of file diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index c98bc0b..95d9c4a 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -7,4 +7,15 @@ target_sources(${PROJECT_NAME} CanDriver.hpp CanId.hpp CanMessage.hpp -) \ No newline at end of file +) + +if (TARGET sockcanpp_test) + target_sources(sockcanpp_test + PUBLIC FILE_SET HEADERS + BASE_DIRS ${CMAKE_CURRENT_LIST_DIR} + FILES + CanDriver.hpp + CanId.hpp + CanMessage.hpp + ) +endif() \ No newline at end of file diff --git a/include/CanId.hpp b/include/CanId.hpp index 92d51df..d17f9f4 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -47,124 +47,115 @@ namespace sockcanpp { */ struct CanId { public: // +++ Constructors +++ - CanId(const CanId& orig): _identifier(orig._identifier), _isErrorFrame(orig._isErrorFrame), - _isRemoteTransmissionRequest(orig._isRemoteTransmissionRequest), _isStandardFrameId(orig._isStandardFrameId), - _isExtendedFrameId(orig._isExtendedFrameId) { /* copy */ } - - CanId(const uint32_t identifier): _identifier(identifier) { - // TODO: Switch to using bitmasks! - - if (isValidIdentifier(identifier)) { - if (((int32_t)log2(identifier) + 1) < 11) { - _isStandardFrameId = true; - } else { _isExtendedFrameId = true; } - } else if (isErrorFrame(identifier)) { - _isErrorFrame = true; - } else if (isRemoteTransmissionRequest(identifier)) { - _isRemoteTransmissionRequest = true; - } - } - - CanId(): _identifier(0), _isStandardFrameId(true) { } + constexpr CanId(const CanId& id) = default; + constexpr CanId() = default; + constexpr CanId(const canid_t id): m_identifier(id) { } + constexpr CanId(const int32_t id): m_identifier(id) { } public: // +++ Operators +++ + constexpr canid_t operator *() const { return m_identifier; } //!< Returns the raw CAN ID value. -#pragma region "Implicit Cast Operators" - operator int16_t() const { return isStandardFrameId() ? (int16_t)_identifier : throw system_error(error_code(0xbad1d, generic_category()), "INVALID CAST: ID is extended or invalid!"); } - operator uint16_t() const { return isStandardFrameId() ? (uint16_t)_identifier : throw system_error(error_code(0xbad1d, generic_category()), "INVALID CAST: ID is extended or invalid!"); } - operator int32_t() const { return _identifier; } - operator uint32_t() const { return _identifier; } +#pragma region "Conversions" + constexpr operator int16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } + constexpr operator uint16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } + constexpr operator int32_t() const { return m_identifier & CAN_ERR_MASK; } + constexpr operator canid_t() const { return m_identifier & CAN_ERR_MASK; } #pragma endregion #pragma region "Bitwise Operators" - CanId operator &(CanId& x) const { return _identifier & x._identifier; } - CanId operator &(const CanId x) const { return _identifier & x._identifier; } - CanId operator &(const int16_t x) const { return _identifier & x; } - CanId operator &(const uint16_t x) const { return _identifier & x; } - CanId operator &(const int32_t x) const { return _identifier & x; } - CanId operator &(const uint32_t x) const { return _identifier & x; } - CanId operator &(const int64_t x) const { return _identifier & x; } - CanId operator &(const uint64_t x) const { return _identifier & x; } - - CanId operator |(CanId& x) const { return _identifier | x._identifier; } - CanId operator |(const CanId x) const { return _identifier | x._identifier; } - CanId operator |(const int16_t x) const { return _identifier | x; } - CanId operator |(const uint16_t x) const { return _identifier | x; } - CanId operator |(const int32_t x) const { return _identifier | x; } - CanId operator |(const uint32_t x) const { return _identifier | x; } - CanId operator |(const int64_t x) const { return _identifier | x; } - CanId operator |(const uint64_t x) const { return _identifier | x; } + template + constexpr CanId operator &(const T x) const { return m_identifier & x; } //!< Performs a bitwise AND operation on this ID and another. + constexpr CanId operator &(const CanId& x) const { return m_identifier & x.m_identifier; } //!< Performs a bitwise AND operation on this ID and another. + + template + constexpr CanId operator |(const T x) const { return m_identifier | x; } //!< Performs a bitwise OR operation on this ID and a 16-bit integer. + constexpr CanId operator |(const CanId& x) const { return m_identifier | x.m_identifier; } //!< Performs a bitwise OR operation on this ID and another. + + template + constexpr CanId operator ^(const T x) const { return m_identifier ^ x; } //!< Performs a bitwise XOR operation on this ID and a 16-bit integer. + constexpr CanId operator ^(const CanId& x) const { return m_identifier ^ x.m_identifier; } //!< Performs a bitwise XOR operation on this ID and another. + + constexpr CanId operator ~() const { return ~m_identifier; } //!< Performs a bitwise NOT operation on this ID. + + template + constexpr CanId operator <<(const T x) const { return m_identifier << x; } //!< Shifts this ID to the left by a 16-bit integer. + constexpr CanId operator <<(const CanId& x) const { return m_identifier << x.m_identifier; } //!< Shifts this ID to the left by another. + + template + constexpr CanId operator >>(const T x) const { return m_identifier >> x; } //!< Shifts this ID to the right by a 16-bit integer. + constexpr CanId operator >>(const CanId& x) const { return m_identifier >> x.m_identifier; } //!< Shifts this ID to the right by another. + + template + CanId operator <<=(const T x) { return m_identifier <<= x; } //!< Shifts this ID to the left by a 16-bit integer. + CanId operator <<=(const CanId& x) { return m_identifier <<= x.m_identifier; } //!< Shifts this ID to the left by another. + + template + CanId operator >>=(const T x) { return m_identifier >>= x; } //!< Shifts this ID to the right by a 16-bit integer. + CanId operator >>=(const CanId& x) { return m_identifier >>= x.m_identifier; } //!< Shifts this ID to the right by another. + #pragma endregion #pragma region "Comparison Operators" - bool operator ==(CanId& x) const { return _identifier == x._identifier; } - bool operator ==(const CanId& x) const { return _identifier == x._identifier; } - bool operator ==(const int16_t x) const { return _identifier == x; } - bool operator ==(const uint16_t x) const { return _identifier == x; } - bool operator ==(const int32_t x) const { return _identifier == x; } - bool operator ==(const uint32_t x) const { return _identifier == x; } - bool operator ==(const int64_t x) const { return (x > UINT32_MAX || x < INT32_MIN) ? false : x == _identifier; } - bool operator ==(const uint64_t x) const { return x > UINT32_MAX ? false : x == _identifier; } - bool operator !=(CanId& x) const { return _identifier != x._identifier; } - bool operator !=(const CanId& x) const { return _identifier != x._identifier; } - bool operator !=(const int16_t x) const { return _identifier != x; } - bool operator !=(const uint16_t x) const { return _identifier != x; } - bool operator !=(const int32_t x) const { return _identifier != x; } - bool operator !=(const uint32_t x) const { return _identifier != x; } - bool operator !=(const int64_t x) const { return (x > UINT32_MAX || x < INT32_MIN) ? false : x != _identifier; } - bool operator !=(const uint64_t x) const { return x > UINT32_MAX ? false : x != _identifier; } - - bool operator <(CanId& x) const { return x._identifier < _identifier; } - bool operator <(int32_t x) const { return x < _identifier; } - bool operator <(uint32_t x) const { return x < _identifier; } - bool operator <(int16_t x) const { return x < _identifier; } - bool operator <(uint16_t x) const { return x < _identifier; } - bool operator <=(CanId& x) const { return x._identifier <= _identifier; } - bool operator >(CanId& x) const { return x._identifier > _identifier; } - bool operator >(int32_t x) const { return x > _identifier; } - bool operator >(uint32_t x) const { return x > _identifier; } - bool operator >(int16_t x) const { return x > _identifier; } - bool operator >(uint16_t x) const { return x > _identifier; } - bool operator >=(CanId& x) const { return x._identifier >= _identifier; } - bool operator <(const CanId& x) const { return x._identifier < _identifier; } - bool operator <=(const CanId& x) const { return x._identifier <= _identifier; } - bool operator >(const CanId& x) const { return x._identifier > _identifier; } - bool operator >=(const CanId& x) const { return x._identifier >= _identifier; } + constexpr bool operator ==(const CanId& x) const { return m_identifier == x.m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator ==(const T x) const { return m_identifier == static_cast(x); } //!< Compares this ID to another. + + constexpr bool operator !=(const CanId& x) const { return m_identifier != x.m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator !=(const T x) const { return m_identifier != static_cast(x); } //!< Compares this ID to another. + + template + constexpr bool operator <(T x) const { return static_cast(x) < m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator >(T x) const { return static_cast(x) > m_identifier; } //!< Compares this ID to a 32-bit integer. + + template + constexpr bool operator <=(const T x) const { return x.m_identifier <= m_identifier; } //!< Compares this ID to another. + + template + constexpr bool operator >=(const T x) const { return x.m_identifier >= m_identifier; } //!< Compares this ID to another. #pragma endregion #pragma region "Assignment Operators" - CanId operator =(const int32_t val) { - uint32_t tmpVal = val; - auto tmp = (isValidIdentifier(tmpVal) ? CanId(val) : throw system_error(error_code(0x5421, generic_category()), "INVALID CAST: ID is extended or invalid!")); - return tmp; - } - - CanId operator =(const uint32_t val) { - uint32_t tmp = val; - return (isValidIdentifier(tmp) ? CanId(val) : throw system_error(error_code(0x5421, generic_category()), "INVALID CAST: ID is extended or invalid!")); - } - - CanId operator =(const int64_t val) { return operator =((int32_t)val); } + template + constexpr CanId operator =(const T val) { return CanId(val); } //!< Assigns a new integer to this CanID + + constexpr CanId operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. #pragma endregion #pragma region "Arithmetic Operators" - CanId operator +(CanId& x) const { return _identifier + x._identifier; } - CanId operator +(const CanId& x) const { return _identifier + x._identifier; } - CanId operator +(const int16_t x) const { return _identifier + x; } - CanId operator +(const uint16_t x) const { return _identifier + x; } - CanId operator +(const int32_t x) const { return _identifier + x; } - CanId operator +(const uint32_t x) const { return _identifier + x; } - CanId operator +(const int64_t x) const { return _identifier + x; } - CanId operator +(const uint64_t x) const { return _identifier + x; } - - CanId operator -(CanId& x) const { return _identifier - x._identifier; } - CanId operator -(const CanId& x) const { return _identifier - x._identifier; } - CanId operator -(const int16_t x) const { return _identifier - x; } - CanId operator -(const uint16_t x) const { return _identifier - x; } - CanId operator -(const int32_t x) const { return _identifier - x; } - CanId operator -(const uint32_t x) const { return _identifier - x; } - CanId operator -(const int64_t x) const { return _identifier - x; } - CanId operator -(const uint64_t x) const { return _identifier - x; } + template + constexpr CanId operator +(const T x) const { return m_identifier + x; } + + template + constexpr CanId operator +=(const T x) { return m_identifier += x; } + + template + constexpr CanId operator -=(const T x) { return m_identifier -= x; } + + template + constexpr CanId operator -(const T x) const { return m_identifier - x; } + + template + constexpr CanId operator *(const T x) const { return m_identifier * x; } + + template + constexpr CanId operator *= (const T x) { return m_identifier *= x; } + + template + constexpr CanId operator /(const T x) const { return m_identifier / x; } + + template + constexpr CanId operator /=(const T x) { return m_identifier /= x; } + + template + constexpr CanId operator %(const T x) const { return m_identifier % x; } + + template + constexpr CanId operator %=(const T x) { return m_identifier %= x; } #pragma endregion public: // +++ Validity Checks +++ @@ -176,16 +167,9 @@ namespace sockcanpp { * @return true If value is a valid CAN identifier. * @return false Otherwise. */ - static bool isValidIdentifier(uint32_t value) { - int32_t tmpValue = ((int32_t)log2(value) + 2); // Get bit count - - // Check for extended frame flag - if (tmpValue >= 29) { - value = (value & CAN_EFF_FLAG) ? (value & CAN_EFF_MASK) : (value & CAN_SFF_MASK); - tmpValue = ((int32_t)log2(value) + 1); // Get bit count again - } - - return (value == 0) /* Default value, also valid ID */ || ((tmpValue <= 29 && tmpValue > 0)); + template + static constexpr bool isValidIdentifier(T value) { + return static_cast(value) <= CAN_EFF_MASK; } /** @@ -196,9 +180,9 @@ namespace sockcanpp { * @return true If value has the error frame flag (bit) set to 1. * @return false Otherwise. */ - static bool isErrorFrame(uint32_t value) { - try { return bitset(value).test(29); } - catch (...) { return false; /* Brute-force, but works. */ } + template + static constexpr bool isErrorFrame(T value) { + return static_cast(value) & CAN_ERR_FLAG; } /** @@ -209,27 +193,35 @@ namespace sockcanpp { * @return true If the frame is a remote transmission request. * @return false Otherwise. */ - static bool isRemoteTransmissionRequest(uint32_t value) { - try { return bitset(value).test(30); } - catch (...) { return false; /* Brute-force, but works. */ } + template + static constexpr bool isRemoteTransmissionRequest(T value) { + return static_cast(value) & CAN_RTR_FLAG; + } + + /** + * @brief Indicates whether or not a given integer is an extended frame ID. + * + * @param value The integer to check. + * + * @return true If the frame is in the extended format. + * @return false Otherwise. + */ + template + static constexpr bool isExtendedFrame(T value) { + return static_cast(value) & CAN_EFF_FLAG; } public: // +++ Getters +++ - bool hasErrorFrameFlag() const { return _isErrorFrame; } - bool hasRtrFrameFlag() const { return _isRemoteTransmissionRequest; } - bool isStandardFrameId() const { return _isStandardFrameId; } - bool isExtendedFrameId() const { return _isExtendedFrameId; } + constexpr bool hasErrorFrameFlag() const { return isErrorFrame(m_identifier); } //!< Indicates whether or not this ID is an error frame. + constexpr bool hasRtrFrameFlag() const { return isRemoteTransmissionRequest(m_identifier); } //!< Indicates whether or not this ID is a remote transmission request. + constexpr bool isStandardFrameId() const { return !isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is a standard frame ID. + constexpr bool isExtendedFrameId() const { return isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is an extended frame ID. public: // +++ Equality Checks +++ - bool equals(CanId otherId) const { return *this == otherId; } + constexpr bool equals(const CanId& otherId) const { return m_identifier == otherId.m_identifier; } //!< Compares this ID to another. private: // +++ Variables +++ - bool _isErrorFrame = false; - bool _isRemoteTransmissionRequest = false; - bool _isStandardFrameId = false; - bool _isExtendedFrameId = false; - - uint32_t _identifier = 0; + uint32_t m_identifier = 0; }; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c45566..12dad56 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,3 +2,14 @@ target_sources(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp ) + +if (TARGET sockcanpp_test) + target_sources(sockcanpp_test + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/CanDriver.cpp + ) +endif() + +add_compile_options( + -Wno-unknown-pragmas +) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ba94a46..7a18e6c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,29 +1,4 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.12) -project(testlibsockcanpp LANGUAGES CXX VERSION 1.0.0) -set(TARGET_NAME testsockcanpp.bin) - -set(CMAKE_CXX_STANDARD 14) - -include_directories( - include/ -) - -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) - -file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) - -add_executable(${TARGET_NAME} ${FILES}) - -target_link_libraries( - # Binary - ${TARGET_NAME} - - # Libs - sockcanpp - -lm - - -static - -static-libstdc++ - -static-libgcc -) \ No newline at end of file +add_subdirectory(app) +add_subdirectory(unit) \ No newline at end of file diff --git a/test/app/CMakeLists.txt b/test/app/CMakeLists.txt new file mode 100644 index 0000000..03938a7 --- /dev/null +++ b/test/app/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required(VERSION 3.14) + +project(testlibsockcanpp LANGUAGES CXX VERSION 1.0.0) +set(TARGET_NAME testsockcanpp.bin) + +set(CMAKE_CXX_STANDARD 14) + +include_directories( + include/ +) + +if (NOT TARGET sockcanpp) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) +endif() + +file(GLOB_RECURSE FILES ${CMAKE_CURRENT_SOURCE_DIR} src/*.cpp) + +add_executable(${TARGET_NAME} ${FILES}) + +target_link_libraries( + # Binary + ${TARGET_NAME} + + # Libs + sockcanpp_test + -lm + + -static + -static-libstdc++ + -static-libgcc +) \ No newline at end of file diff --git a/test/src/Main.cpp b/test/app/src/Main.cpp similarity index 100% rename from test/src/Main.cpp rename to test/app/src/Main.cpp diff --git a/test/unit/.gitignore b/test/unit/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/test/unit/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt new file mode 100644 index 0000000..ff313e9 --- /dev/null +++ b/test/unit/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.10) + +project(libsockcanpp_unittests LANGUAGES CXX VERSION 0.1) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +find_package(GTest REQUIRED) + +include_directories( + # none as of now +) + +if (NOT TARGET sockcanpp) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_BINARY_DIR}/libsockcanpp) +endif() + +file(GLOB_RECURSE SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp) + +add_compile_options( + -Wall + -Wextra + -Wpedantic + -Werror + -Wno-unknown-pragmas +) + +add_executable(${PROJECT_NAME} ${SOURCES}) + +target_link_libraries( + ${PROJECT_NAME} + + sockcanpp_test + ${GTEST_LIBRARIES} + pthread +) + +gtest_discover_tests(${PROJECT_NAME}) \ No newline at end of file diff --git a/test/unit/src/CanId_Tests.cpp b/test/unit/src/CanId_Tests.cpp new file mode 100644 index 0000000..7eb621d --- /dev/null +++ b/test/unit/src/CanId_Tests.cpp @@ -0,0 +1,278 @@ +/** + * @file CanId_Tests.cpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains all the unit tests for the CanId structure. + * @version 0.1 + * @date 2024-05-29 + * + * @copyright Copyright (c) 2024 Simon Cahill and Contributors. + */ + +#include + +#include + +using sockcanpp::CanId; + +TEST(CanIdTests, CanId_invalidId_ExpectFalse) { + ASSERT_FALSE(CanId::isValidIdentifier(-1)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId) { + ASSERT_TRUE(CanId::isValidIdentifier(0x123)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId) { + ASSERT_TRUE(CanId::isValidIdentifier(0x123456)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCast) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCast) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCast) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCast) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToInt16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((int16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToUint16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((uint16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToInt32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToInt16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToUint16) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToInt32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToInt16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((int16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToUint16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((uint16_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToInt32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((int32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToInt16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToUint16) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToInt32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ExplicitCastToUint32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier((uint32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ExplicitCastToUint32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier((uint32_t)id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_StandardFrameId_ImplicitCastToUint32) { + CanId id(0x123); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_validId_ExpectTrue_ExtendedFrameId_ImplicitCastToUint32) { + CanId id(0x123456); + ASSERT_TRUE(CanId::isValidIdentifier(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue) { + auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isErrorFrame(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse) { + auto id = 0x123; + ASSERT_FALSE(CanId::isErrorFrame(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue_ExplicitCast) { + auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isErrorFrame((int32_t)id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse_ExplicitCast) { + auto id = 0x123; + ASSERT_FALSE(CanId::isErrorFrame((int32_t)id)); +} + +TEST(CanIdTests, CanId_isExtendedFrame_ExpectTrue) { + auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isExtendedFrame(id)); +} + +TEST(CanIdTests, CanId_isExtendedFrame_ExpectFalse) { + auto id = 0x123; + ASSERT_FALSE(CanId::isExtendedFrame(id)); +} + +TEST(CanIdTests, CanId_isRtr_ExpectTrue) { + auto id = 0x40000000; + ASSERT_TRUE(CanId::isRemoteTransmissionRequest(id)); +} + +TEST(CanIdTests, CanId_isRtr_ExpectFalse) { + auto id = 0x123; + ASSERT_FALSE(CanId::isRemoteTransmissionRequest(id)); +} + +// Test constexpr +TEST(CanIdTests, CanId_isErrorFrame_ExpectTrue_Constexpr) { + constexpr auto id = 0xe0000abc; + ASSERT_TRUE(CanId::isErrorFrame(id)); +} + +TEST(CanIdTests, CanId_isErrorFrame_ExpectFalse_Constexpr) { + constexpr auto id = 0x123; + ASSERT_FALSE(CanId::isErrorFrame(id)); +} + +// Test implicit operators +// Implicit operators strip out control bits + +TEST(CanIdTests, CanId_ImplicitOperatorInt32_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((int32_t)id, 0x123); +} + +TEST(CanIdTests, CanId_ImplicitOperatorInt16_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((int16_t)id, 0x123); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint16_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((uint16_t)id, 0x123); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint32_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ((uint32_t)id, 0x123); +} + +// Test implicit operators with control bits set; these should be stripped. +TEST(CanIdTests, CanId_ImplicitOperatorInt32_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((int32_t)id, 0x12345678); +} + +TEST(CanIdTests, CanId_ImplicitOperatorInt16_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((int16_t)id, 0x5678); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint16_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((uint16_t)id, 0x5678); +} + +TEST(CanIdTests, CanId_ImplicitOperatorUint32_ExpectTrue_ControlBits) { + CanId id(0x12345678); + ASSERT_EQ((uint32_t)id, 0x12345678); +} + +// Test arithmetic operators +TEST(CanIdTests, CanId_ArithmeticOperatorPlus_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id + 0x123, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorMinus_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id - 0x123, 0); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorPlusEquals_ExpectTrue) { + CanId id(0x123); + id += 0x123; + ASSERT_EQ(id, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorMinusEquals_ExpectTrue) { + CanId id(0x123); + id -= 0x123; + ASSERT_EQ(id, 0); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorStar_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id * 2, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorSlash_ExpectTrue) { + CanId id(0x246); + ASSERT_EQ(id / 2, 0x123); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorStarEquals_ExpectTrue) { + CanId id(0x123); + id *= 2; + ASSERT_EQ(id, 0x246); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorSlashEquals_ExpectTrue) { + CanId id(0x246); + id /= 2; + ASSERT_EQ(id, 0x123); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorModulo_ExpectTrue) { + CanId id(0x123); + ASSERT_EQ(id % 2, 1); +} + +TEST(CanIdTests, CanId_ArithmeticOperatorModuloEquals_ExpectTrue) { + CanId id(0x123); + id %= 2; + ASSERT_EQ(id, 1); +} \ No newline at end of file diff --git a/test/unit/src/main.cpp b/test/unit/src/main.cpp new file mode 100644 index 0000000..49c7a31 --- /dev/null +++ b/test/unit/src/main.cpp @@ -0,0 +1,16 @@ +/** + * @file main.cpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the main entry point for the unit tests. + * @version 0.1 + * @date 2024-05-29 + * + * @copyright Copyright (c) 2024 Simon Cahill and Contributors. + */ + +#include + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file From bff1c8a2238580d82056dbb41e17a0feda18f00a Mon Sep 17 00:00:00 2001 From: SimonC Date: Sun, 28 Jul 2024 19:14:33 +0200 Subject: [PATCH 04/29] Updated e-mail address (#18) --- include/CanDriver.hpp | 2 +- include/CanId.hpp | 2 +- include/CanMessage.hpp | 2 +- include/exceptions/CanCloseException.hpp | 2 +- include/exceptions/CanException.hpp | 2 +- include/exceptions/CanInitException.hpp | 2 +- include/exceptions/InvalidSocketException.hpp | 2 +- src/CanDriver.cpp | 2 +- test/app/src/Main.cpp | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index 7dc1b5d..79292ff 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -1,6 +1,6 @@ /** * @file CanDriver.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the declarations for the SocketCAN wrapper in C++. * @version 0.1 * @date 2020-07-01 diff --git a/include/CanId.hpp b/include/CanId.hpp index d17f9f4..e0a4040 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -1,6 +1,6 @@ /** * @file CanId.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a value-type representing a CAN identifier. * @version 0.1 * @date 2020-07-01 diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index dbf0ff3..0825173 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -1,6 +1,6 @@ /** * @file CanMessage.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a CAN message representation in C++. * @version 0.1 * @date 2020-07-01 diff --git a/include/exceptions/CanCloseException.hpp b/include/exceptions/CanCloseException.hpp index 854d4cb..4bd366f 100644 --- a/include/exceptions/CanCloseException.hpp +++ b/include/exceptions/CanCloseException.hpp @@ -1,6 +1,6 @@ /** * @file CanCloseException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of an exception that may be thrown when an error occurs while closing a CAN socket. * @version 0.1 * @date 2020-07-02 diff --git a/include/exceptions/CanException.hpp b/include/exceptions/CanException.hpp index 3c71f5b..605781c 100644 --- a/include/exceptions/CanException.hpp +++ b/include/exceptions/CanException.hpp @@ -1,6 +1,6 @@ /** * @file CanException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a general-purpose exception which may be thrown when an error occurs when performing IO on a CAN socket. * @version 0.1 * @date 2020-07-02 diff --git a/include/exceptions/CanInitException.hpp b/include/exceptions/CanInitException.hpp index 8f3707a..443991d 100644 --- a/include/exceptions/CanInitException.hpp +++ b/include/exceptions/CanInitException.hpp @@ -1,6 +1,6 @@ /** * @file CanInitException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of an exception that is thrown when a CAN socket couldn't be inintialised. * @version 0.1 * @date 2020-07-02 diff --git a/include/exceptions/InvalidSocketException.hpp b/include/exceptions/InvalidSocketException.hpp index f7ab7dd..9a7c352 100644 --- a/include/exceptions/InvalidSocketException.hpp +++ b/include/exceptions/InvalidSocketException.hpp @@ -1,6 +1,6 @@ /** * @file InvalidSocketException.hpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of an exception that may be thrown when an invalid CAN socket is detected. * @version 0.1 * @date 2020-07-02 diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 990d02b..4b33f5e 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -1,6 +1,6 @@ /** * @file CanDriver.cpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief * @version 0.1 * @date 2020-07-01 diff --git a/test/app/src/Main.cpp b/test/app/src/Main.cpp index db76170..288d20f 100644 --- a/test/app/src/Main.cpp +++ b/test/app/src/Main.cpp @@ -1,6 +1,6 @@ /** * @file Main.cpp - * @author Simon Cahill (simonc@online.de) + * @author Simon Cahill (contact@simonc.eu) * @brief Contains the implementation of a test application using this library. * @version 0.1 * @date 2020-07-02 From 132171ca2ec5b17dcd6aae54a3d2659ecccf5137 Mon Sep 17 00:00:00 2001 From: SimonC Date: Fri, 20 Dec 2024 16:48:59 +0100 Subject: [PATCH 05/29] Fix/can filters (#20) * Updated .gitignore; now ignores all build dirs. * Updated version and updated test build * Added fix and improvements from setCanFilterMask #19 * Added hasher for CanId for unordered_map --- .gitignore | 2 +- CMakeLists.txt | 5 +++- include/CanDriver.hpp | 59 +++++++++++++++++++++++++------------------ include/CanId.hpp | 8 ++++++ src/CanDriver.cpp | 55 ++++++++++++++++++++++++++-------------- 5 files changed, 83 insertions(+), 46 deletions(-) diff --git a/.gitignore b/.gitignore index fdd0afb..f27cdbf 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ ### # Build directories ### -build/ +build*/ ### # Documentation (Doxygen) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf1471e..faea963 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,12 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.1.0) +project(sockcanpp LANGUAGES CXX VERSION 1.2.0) option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" ON) option(BUILD_TESTS "Build the tests" OFF) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + set(CMAKE_CXX_STANDARD 11) include(GNUInstallDirs) @@ -21,6 +23,7 @@ endif() ### if(BUILD_TESTS STREQUAL "ON") add_library(${PROJECT_NAME}_test STATIC) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/) endif() add_subdirectory(include) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index 79292ff..8dc0efb 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -32,6 +32,7 @@ #include #include #include +#include ////////////////////////////// // LOCAL INCLUDES // @@ -50,6 +51,9 @@ namespace sockcanpp { using std::mutex; using std::string; using std::queue; + using std::unordered_map; + + using filtermap_t = unordered_map; /** * @brief CanDriver class; handles communication via CAN. @@ -68,50 +72,55 @@ namespace sockcanpp { public: // +++ Constructor / Destructor +++ CanDriver(const string& canInterface, const int32_t canProtocol, const CanId defaultSenderId = 0); //!< Constructor CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); + CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId = 0); CanDriver() = default; virtual ~CanDriver() { uninitialiseSocketCan(); } //!< Destructor public: // +++ Getter / Setter +++ - CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID + CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID - CanId getDefaultSenderId() const { return this->_defaultSenderId; } //!< Gets the default sender ID + CanId getDefaultSenderId() const { return this->_defaultSenderId; } //!< Gets the default sender ID - int32_t getFilterMask() const { return this->_canFilterMask; } //!< Gets the filter mask used by this instance - int32_t getMessageQueueSize() const { return this->_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() - int32_t getSocketFd() const { return this->_socketFd; } //!< The socket file descriptor used by this instance. + filtermap_t getFilterMask() const { return this->_canFilterMask; } //!< Gets the filter mask used by this instance + int32_t getMessageQueueSize() const { return this->_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() + int32_t getSocketFd() const { return this->_socketFd; } //!< The socket file descriptor used by this instance. public: // +++ I/O +++ - virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); //!< Waits for CAN messages to appear + virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); //!< Waits for CAN messages to appear - virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus + virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus - virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message - virtual ssize_t sendMessageQueue(queue messages, - milliseconds delay = milliseconds(20), bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message + virtual ssize_t sendMessageQueue(queue messages, milliseconds delay = milliseconds(20), bool forceExtended = false); //!< Attempts to send a queue of messages - virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus + virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus - virtual void setCanFilterMask(const int32_t mask); //!< Attempts to set a new CAN filter mask to the BIOS + virtual void setCanFilterMask(const int32_t mask, const CanId& filterId); //!< Attempts to set a new CAN filter mask to the interface + virtual void setCanFilters(const filtermap_t& filters); //!< Sets the CAN filters for the interface protected: // +++ Socket Management +++ - virtual void initialiseSocketCan(); //!< Initialises socketcan - virtual void uninitialiseSocketCan(); //!< Uninitialises socketcan + virtual void initialiseSocketCan(); //!< Initialises socketcan + virtual void uninitialiseSocketCan(); //!< Uninitialises socketcan - private: - virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard - CanId _defaultSenderId; //!< The ID to send messages with if no other ID was set. + private: // +++ Member Functions +++ + virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard - int32_t _canFilterMask; //!< The bit mask used to filter CAN messages - int32_t _canProtocol; //!< The protocol used when communicating via CAN - int32_t _socketFd{-1}; //!< The CAN socket file descriptor - int32_t _queueSize{0}; ///!< The size of the message queue read by waitForMessages() + private: // +++ Variables +++ + + CanId _defaultSenderId; //!< The ID to send messages with if no other ID was set. - //!< Mutex for thread-safety. - mutex _lock{}; - mutex _lockSend{}; + filtermap_t _canFilterMask; //!< The bit mask used to filter CAN messages + + int32_t _canProtocol; //!< The protocol used when communicating via CAN + int32_t _socketFd{-1}; //!< The CAN socket file descriptor + int32_t _queueSize{0}; ///!< The size of the message queue read by waitForMessages() - string _canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) + //!< Mutex for thread-safety. + mutex _lock{}; + mutex _lockSend{}; + string _canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) + }; diff --git a/include/CanId.hpp b/include/CanId.hpp index e0a4040..4e09132 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -224,6 +224,14 @@ namespace sockcanpp { uint32_t m_identifier = 0; }; + /** + * @brief Implements a hash function for the CanId type. + */ + struct CanIdHasher { + public: + size_t operator()(const CanId& id) const { return std::hash()(*id); } + }; + } #endif // LIBSOCKPP_INCLUDE_CANID_HPP diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 4b33f5e..c8dbc88 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -43,6 +43,7 @@ #include #include #include +#include ////////////////////////////// // LOCAL INCLUDES // @@ -70,23 +71,27 @@ namespace sockcanpp { using std::unique_ptr; using std::chrono::milliseconds; using std::this_thread::sleep_for; + using std::vector; ////////////////////////////////////// // PUBLIC IMPLEMENTATION // ////////////////////////////////////// -#pragma region Object Construction +#pragma region "Object Construction" CanDriver::CanDriver(const string& canInterface, int32_t canProtocol, const CanId defaultSenderId): - CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) {} + CanDriver(canInterface, canProtocol, 0 /* match all */, defaultSenderId) { } CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId): - _defaultSenderId(defaultSenderId), _canFilterMask(filterMask), _canProtocol(canProtocol), _canInterface(canInterface) { + CanDriver(canInterface, canProtocol, filtermap_t{{0, filterMask}}, defaultSenderId) { } + + CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId): + _defaultSenderId(defaultSenderId), _canFilterMask(filters), _canProtocol(canProtocol), _canInterface(canInterface) { initialiseSocketCan(); } #pragma endregion -#pragma region I / O +#pragma region "I / O" /** * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. * @@ -125,9 +130,7 @@ namespace sockcanpp { * * @return CanMessage The message read from the bus. */ - CanMessage CanDriver::readMessage() { - return readMessageLock(); - } + CanMessage CanDriver::readMessage() { return readMessageLock(); } /** * @brief readMessage deadlock guard, attempts to read a message from the associated CAN bus. @@ -137,9 +140,7 @@ namespace sockcanpp { CanMessage CanDriver::readMessageLock(bool const lock) { unique_ptr> locky{nullptr}; - if (lock) { - locky = unique_ptr>{new unique_lock{_lock}}; - } + if (lock) { locky = unique_ptr>{new unique_lock{_lock}}; } if (0 > _socketFd) { throw InvalidSocketException("Invalid socket!", _socketFd); } @@ -229,21 +230,37 @@ namespace sockcanpp { * @brief Attempts to set the filter mask for the associated CAN bus. * * @param mask The bit mask to apply. + * @param filterId The ID to filter on. */ - void CanDriver::setCanFilterMask(const int32_t mask) { + void CanDriver::setCanFilterMask(const int32_t mask, const CanId& filterId) { + setCanFilters({{filterId, static_cast(mask)}}); + } + + /** + * @brief Sets multiple CAN filters for the associated CAN bus. + * + * @param filters A map containing the filters to apply. + */ + void CanDriver::setCanFilters(const filtermap_t& filters) { if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } unique_lock locky(_lock); - can_filter canFilter; - - canFilter.can_id = _defaultSenderId; - canFilter.can_mask = mask; + vector canFilters{}; - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, &canFilter, sizeof(canFilter)) == -1) { - throw CanInitException(formatString("FAILED to set CAN filter mask %x on socket %d! Error: %d => %s", mask, _socketFd, errno, strerror(errno))); + // Structured bindings only available with C++17 + #if __cplusplus >= 201703L + for (const auto [id, filter] : filters) { + canFilters.push_back({id, filter}); } + #else + for (const auto& filterPair : filters) { + canFilters.push_back({filterPair.first, filterPair.second}); + } + #endif - _canFilterMask = mask; + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, canFilters.data(), canFilters.size() * sizeof(can_filter)) == -1) { + throw CanInitException(formatString("FAILED to set CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + } } #pragma endregion @@ -287,7 +304,7 @@ namespace sockcanpp { address.can_family = AF_CAN; address.can_ifindex = ifaceRequest.ifr_ifindex; - setCanFilterMask(_canFilterMask); + setCanFilters(_canFilterMask); if ((tmpReturn = bind(_socketFd, (struct sockaddr*)&address, sizeof(address))) == -1) { throw CanInitException(formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno))); From 6548a99813262cf2d313a3ca6b7bd478af755e1c Mon Sep 17 00:00:00 2001 From: SimonC Date: Wed, 5 Feb 2025 23:23:11 +0100 Subject: [PATCH 06/29] Renamed docs target to libsockcan_docs (#21) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index faea963..9e98967 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,9 +65,9 @@ include(cmake/cpack.cmake) ### # Docs target ### -add_custom_target("docs" COMMENT "Create Doxygen documentation") +add_custom_target("libsockcan_docs" COMMENT "Create Doxygen documentation") add_custom_command( - TARGET "docs" + TARGET "libsockcan_docs" POST_BUILD COMMENT "Generate Doxygen documentation for publication or reading" COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile From dc2acaf949df580fabf374ad5c0bbeb34df0a35e Mon Sep 17 00:00:00 2001 From: SimonC Date: Thu, 13 Mar 2025 15:15:05 +0100 Subject: [PATCH 07/29] Added support for string to CanId conversions (well init with string value) (#22) * CMake now dynmically sets required language standard. * Added instructions on how to maintain C++11-compat * Added support for concepts * CanId now supports initialisation with string values (hex only) --------- Co-authored-by: Simon Cahill --- CMakeLists.txt | 19 ++++++- README.md | 9 ++++ include/CanId.hpp | 30 +++++++++++- include/exceptions/CanException.hpp | 10 ++-- include/exceptions/InvalidSocketException.hpp | 10 ++-- test/app/src/Main.cpp | 11 +++-- test/unit/CMakeLists.txt | 2 +- test/unit/src/CanId_Tests.cpp | 49 ++++++++++++++++++- 8 files changed, 121 insertions(+), 19 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e98967..058178f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,24 @@ option(BUILD_TESTS "Build the tests" OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_CXX_STANDARD 11) +option(sockcanpp_CONCEPT_SUPPORT "Allow higher level language support" ON) + +if (sockcanpp_CONCEPT_SUPPORT) + message(STATUS "Enabling support for higher level language features") + set(CMAKE_CXX_STANDARD 20) +else() + set(CMAKE_CXX_STANDARD 11) +endif() + +add_compile_options( + -Wall + -Werror + -Wpedantic +) + +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s") + include(GNUInstallDirs) if (BUILD_SHARED_LIBS STREQUAL "ON") diff --git a/README.md b/README.md index aad9aea..efaf5af 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,12 @@ libsockcanpp is a socketcan wrapper library for C++, aimed to be easy to use wit ## Building +> **C++11** SUPPORT: +> This library supports modern C++ features, such as concepts in certain places. +> If your project cannot support C++20 features, you can force the C++ standard back by setting +> `-Dsockcanpp_CONCEPT_SUPPORT=OFF` in the command-line or `set(sockcanpp_CONCEPT_SUPPORT OFF CACHE BOOL "Force C++ standard back to 11")` +> in your CMakeLists.txt. + libsockcanpp was designed with use in CMake projects, but it can also easily be integrated into existing Makefile projects, as long as cmake is present on the build system. 1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` @@ -26,6 +32,9 @@ libsockcanpp was designed with use in CMake projects, but it can also easily be 2) add the following to CMakeLists.txt ```cmake if (NOT TARGET sockcanpp) + # IF you need C++11 support: + # set(sockcanpp_CONCEPT_SUPPORT OFF CACHE BOOL "Force C++ standard back to 11") + add_subdirectory(/path/to/libsockcanpprepo ${CMAKE_CURRENT_BUILD_DIR}/libsockcanpp) endif() diff --git a/include/CanId.hpp b/include/CanId.hpp index 4e09132..c924a49 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -28,12 +28,27 @@ ////////////////////////////// // SYSTEM INCLUDES // ////////////////////////////// +#include #include #include #include #include #include +#if __cpp_concepts >= 201907 +template +concept Stringable = requires(Str s) { { s.data() + s.size() } -> std::convertible_to; }; + +template +concept CChar = requires(CharArr c) { std::is_same_v; }; + +template +concept Integral = requires(Int i) { std::is_integral_v; }; + +template +concept ConvertibleToCanId = Stringable || Integral || CChar; +#endif + namespace sockcanpp { using std::bitset; @@ -47,11 +62,17 @@ namespace sockcanpp { */ struct CanId { public: // +++ Constructors +++ - constexpr CanId(const CanId& id) = default; constexpr CanId() = default; constexpr CanId(const canid_t id): m_identifier(id) { } constexpr CanId(const int32_t id): m_identifier(id) { } + #if __cpp_concepts >= 201907 + template + CanId(const T& id) { m_identifier = std::stoul(id.data(), nullptr, 16); } + #endif // __cpp_concepts >= 201907 + + CanId(const char* id) { m_identifier = std::stoul(id, nullptr, 16); } + public: // +++ Operators +++ constexpr canid_t operator *() const { return m_identifier; } //!< Returns the raw CAN ID value. @@ -123,6 +144,13 @@ namespace sockcanpp { template constexpr CanId operator =(const T val) { return CanId(val); } //!< Assigns a new integer to this CanID + #if __cpp_concepts >= 201907 + template + CanId operator =(const T& val) { + return operator=(std::stoul(val.data(), nullptr, 16)); + } + #endif // __cpp_concepts >= 201907 + constexpr CanId operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. #pragma endregion diff --git a/include/exceptions/CanException.hpp b/include/exceptions/CanException.hpp index 605781c..ba545d6 100644 --- a/include/exceptions/CanException.hpp +++ b/include/exceptions/CanException.hpp @@ -38,19 +38,19 @@ namespace sockcanpp { namespace exceptions { */ class CanException: public exception { public: // +++ Constructor / Destructor +++ - CanException(string message, int32_t socket): _message(message), _socket(socket) {} + CanException(const string& message, int32_t socket): m_socket(socket), m_message(message) {} ~CanException() {} public: // +++ Overrides +++ - const char* what() { return _message.c_str(); } + const char* what() { return m_message.c_str(); } public: // +++ Getter +++ - const int32_t getSocket() const { return _socket; } + const int32_t getSocket() const { return m_socket; } private: - int32_t _socket; + int32_t m_socket; - string _message; + string m_message; }; } /* exceptions */ } /* sockcanpp */ diff --git a/include/exceptions/InvalidSocketException.hpp b/include/exceptions/InvalidSocketException.hpp index 9a7c352..5fd2c9d 100644 --- a/include/exceptions/InvalidSocketException.hpp +++ b/include/exceptions/InvalidSocketException.hpp @@ -38,19 +38,19 @@ namespace sockcanpp { namespace exceptions { */ class InvalidSocketException: public exception { public: // +++ Constructor / Destructor +++ - InvalidSocketException(string message, int32_t socket): _message(message), _socket(socket) {} + InvalidSocketException(const string& message, int32_t socket): m_socket(socket), m_message(message) {} ~InvalidSocketException() {} public: // +++ Overrides +++ - const char* what() { return _message.c_str(); } + const char* what() { return m_message.c_str(); } public: // +++ Getter +++ - const int32_t getSocket() const { return _socket; } + const int32_t getSocket() const { return m_socket; } private: - int32_t _socket; + int32_t m_socket; - string _message; + string m_message; }; } /* exceptions */ } /* sockcanpp */ diff --git a/test/app/src/Main.cpp b/test/app/src/Main.cpp index 288d20f..fcb2445 100644 --- a/test/app/src/Main.cpp +++ b/test/app/src/Main.cpp @@ -50,14 +50,15 @@ int main(int32_t argCount, char** argValues) { if (argCount > 2) { for (int32_t i = 1; i < argCount; i++) { - if (argValues[i] == "--help" || argValues[i] == "-h") { + string arg{argValues[i]}; + if (arg == "--help" || arg == "-h") { printHelp(argValues[0]); return 0; - } else if (argValues[i] == "-protocol") { + } else if (arg == "-protocol") { desiredCanSocket = atoi(argValues[i + 1]); i += 1; continue; - } else if (argValues[i] == "-iface") { + } else if (arg == "-iface") { canInterface = (argValues[i + 1]); i += 1; continue; @@ -70,12 +71,12 @@ int main(int32_t argCount, char** argValues) { if (canInterface == "") canInterface = "can0"; - CanDriver* canDriver; + CanDriver* canDriver{nullptr}; try { canDriver = new CanDriver(canInterface, CAN_RAW); } catch (CanInitException& ex) { cerr << "An error occurred while initialising CanDriver: " << ex.what() << endl; - delete canDriver; + if (canDriver) { delete canDriver; } return -1; } diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index ff313e9..bfc08a4 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) project(libsockcanpp_unittests LANGUAGES CXX VERSION 0.1) -set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(GTest REQUIRED) diff --git a/test/unit/src/CanId_Tests.cpp b/test/unit/src/CanId_Tests.cpp index 7eb621d..deed8e8 100644 --- a/test/unit/src/CanId_Tests.cpp +++ b/test/unit/src/CanId_Tests.cpp @@ -14,6 +14,8 @@ using sockcanpp::CanId; +using std::string; + TEST(CanIdTests, CanId_invalidId_ExpectFalse) { ASSERT_FALSE(CanId::isValidIdentifier(-1)); } @@ -275,4 +277,49 @@ TEST(CanIdTests, CanId_ArithmeticOperatorModuloEquals_ExpectTrue) { CanId id(0x123); id %= 2; ASSERT_EQ(id, 1); -} \ No newline at end of file +} + +#if __cpp_concepts >= 201907 + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue) { + string id{"0x123"}; + + EXPECT_NO_THROW(CanId canId(id)); + EXPECT_NO_THROW(CanId canId{id}); + EXPECT_NO_THROW(CanId canId = id; (void)canId;); + EXPECT_NO_THROW(CanId canId{"0x123"}); + + CanId canId(id); + ASSERT_EQ(canId, 0x123); +} + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue_ExplicitCast) { + string id{"0x123"}; + + EXPECT_NO_THROW(CanId canId(static_cast(id.c_str()))); + EXPECT_NO_THROW(CanId canId = static_cast(id.c_str()); (void)canId;); + EXPECT_NO_THROW(CanId canId = static_cast(id.c_str()); (void)canId;); + + CanId canId(static_cast(id.c_str())); + ASSERT_EQ(canId, 0x123); +} + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectTrue_ImplicitCast) { + string id{"0x123"}; + + EXPECT_NO_THROW(CanId canId = id; (void)canId;); + EXPECT_NO_THROW(CanId canId = id; (void)canId;); + + CanId canId = id; + ASSERT_EQ(canId, 0x123); +} + +TEST(CanIdTests, CanId_TestStringToCanIdConversion_ExpectError) { + string id{"hello_world"}; + + EXPECT_THROW(CanId canId(id), std::invalid_argument); + EXPECT_THROW(CanId canId = id; (void)canId;, std::invalid_argument); + EXPECT_THROW(CanId canId = id; (void)canId;, std::invalid_argument); +} + +#endif // __cpp_concepts >= 201907 \ No newline at end of file From e9592d75f5375bfdbded638b3dcbaac34e295d21 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Thu, 13 Mar 2025 15:16:08 +0100 Subject: [PATCH 08/29] Updated version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 058178f..55dd0a1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.2.0) +project(sockcanpp LANGUAGES CXX VERSION 1.3.0) option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" ON) option(BUILD_TESTS "Build the tests" OFF) From e2c201dfb4ad76d1ceb100284cddb50637320173 Mon Sep 17 00:00:00 2001 From: SimonC Date: Sun, 23 Mar 2025 22:15:57 +0100 Subject: [PATCH 09/29] Add overloaded functions for more fine-grained timeouts/bulk sending. (#23) * Bumped up version * Fixed test build * Added specialisations/overloads for more duration types; namely microseconds and nanoseconds. Nanoseconds are converted to microseconds (because of the way Linux handles timeouts in select()). Also added option to filter out error messages. * No compiler should nag about unknown pragmas * Added getter for CAN interface * Updated README --------- Co-authored-by: Simon Cahill --- CMakeLists.txt | 5 ++- README.md | 1 + include/CanDriver.hpp | 18 +++++++-- src/CanDriver.cpp | 91 +++++++++++++++++++++++++++++++++---------- test/app/src/Main.cpp | 4 +- 5 files changed, 93 insertions(+), 26 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 55dd0a1..0f7747b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.3.0) +project(sockcanpp LANGUAGES CXX VERSION 1.4.0) -option(BUILD_SHARED_LIBS "Build shared libraries (.dll/.so) instead of static ones (.lib/.a)" ON) +option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) @@ -20,6 +20,7 @@ add_compile_options( -Wall -Werror -Wpedantic + -Wno-unknown-pragmas ) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") diff --git a/README.md b/README.md index efaf5af..da98905 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ libsockcanpp is a socketcan wrapper library for C++, aimed to be easy to use wit ## Building +> [!NOTE] > **C++11** SUPPORT: > This library supports modern C++ features, such as concepts in certain places. > If your project cannot support C++20 features, you can force the C++ standard back by setting diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index 8dc0efb..de28ea1 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -47,7 +47,11 @@ */ namespace sockcanpp { + using namespace std::chrono_literals; + + using std::chrono::microseconds; using std::chrono::milliseconds; + using std::chrono::nanoseconds; using std::mutex; using std::string; using std::queue; @@ -77,26 +81,34 @@ namespace sockcanpp { virtual ~CanDriver() { uninitialiseSocketCan(); } //!< Destructor public: // +++ Getter / Setter +++ - CanDriver& setDefaultSenderId(const CanId id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID + CanDriver& setDefaultSenderId(const CanId& id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID CanId getDefaultSenderId() const { return this->_defaultSenderId; } //!< Gets the default sender ID filtermap_t getFilterMask() const { return this->_canFilterMask; } //!< Gets the filter mask used by this instance + int32_t getMessageQueueSize() const { return this->_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() int32_t getSocketFd() const { return this->_socketFd; } //!< The socket file descriptor used by this instance. + string getCanInterface() const { return this->_canInterface; } //!< The CAN interface used by this instance. + public: // +++ I/O +++ - virtual bool waitForMessages(milliseconds timeout = milliseconds(3000)); //!< Waits for CAN messages to appear + virtual bool waitForMessages(microseconds timeout = 3000us); //!< Waits for CAN messages to appear + virtual bool waitForMessages(milliseconds timeout = 3000ms); //!< Waits for CAN messages to appear + virtual bool waitForMessages(nanoseconds timeout = 3000ns); //!< Waits for CAN messages to appear virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message - virtual ssize_t sendMessageQueue(queue messages, milliseconds delay = milliseconds(20), bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, microseconds delay = 20us, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, milliseconds delay = 20ms, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay = 20ns, bool forceExtended = false); //!< Attempts to send a queue of messages virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus virtual void setCanFilterMask(const int32_t mask, const CanId& filterId); //!< Attempts to set a new CAN filter mask to the interface virtual void setCanFilters(const filtermap_t& filters); //!< Sets the CAN filters for the interface + virtual void setErrorFilter(const bool enabled = true) const; //!< Sets the error filter for the interface protected: // +++ Socket Management +++ virtual void initialiseSocketCan(); //!< Initialises socketcan diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index c8dbc88..59e5d75 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -95,20 +95,19 @@ namespace sockcanpp { /** * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. * - * @param timeout The time (in millis) to wait before timing out. + * @param timeout The time (in µsec) to wait before timing out. * * @return true If messages are available on the bus. * @return false Otherwise. */ - bool CanDriver::waitForMessages(milliseconds timeout) { + bool CanDriver::waitForMessages(microseconds timeout/* = microseconds(3000)*/) { if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } unique_lock locky(_lock); fd_set readFileDescriptors; timeval waitTime; - waitTime.tv_sec = timeout.count() / 1000; - waitTime.tv_usec = timeout.count() * 1000; + waitTime.tv_usec = timeout.count(); FD_ZERO(&readFileDescriptors); FD_SET(_socketFd, &readFileDescriptors); @@ -125,6 +124,26 @@ namespace sockcanpp { return fdsAvailable > 0; } + /** + * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. + * + * @param timeout The time (in millis) to wait before timing out. + * + * @return true If messages are available on the bus. + * @return false Otherwise. + */ + bool CanDriver::waitForMessages(milliseconds timeout) { return waitForMessages(std::chrono::duration_cast(timeout)); } + + /** + * @brief Blocks until one or more CAN messages appear on the bus, or until the timeout runs out. + * + * @param timeout The time (in nanoseconds) to wait before timing out. + * + * @return true If messages are available on the bus. + * @return false Otherwise. + */ + bool CanDriver::waitForMessages(nanoseconds timeout/* = nanoseconds(3000)*/) { return waitForMessages(std::chrono::duration_cast(timeout)); } + /** * @brief Attempts to read a message from the associated CAN bus. * @@ -147,8 +166,6 @@ namespace sockcanpp { ssize_t readBytes{0}; can_frame canFrame{}; - memset(&canFrame, 0, sizeof(can_frame)); - readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); if (0 > readBytes) { throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); } @@ -195,7 +212,29 @@ namespace sockcanpp { * * @return int32_t The total amount of bytes sent. */ - ssize_t CanDriver::sendMessageQueue(queue messages, milliseconds delay, bool forceExtended) { + ssize_t CanDriver::sendMessageQueue(queue& messages, microseconds delay, bool forceExtended) { return sendMessageQueue(messages, std::chrono::duration_cast(delay), forceExtended); } + + /** + * @brief Attempts to send a queue of messages on the associated CAN bus. + * + * @param messages A queue containing the messages to be sent. + * @param delay If greater than 0, will delay the sending of the next message. + * @param forceExtended Whether or not to force use of an extended ID. + * + * @return int32_t The total amount of bytes sent. + */ + ssize_t CanDriver::sendMessageQueue(queue& messages, milliseconds delay, bool forceExtended) { return sendMessageQueue(messages, std::chrono::duration_cast(delay), forceExtended); } + + /** + * @brief Attempts to send a queue of messages on the associated CAN bus. + * + * @param messages A queue containing the messages to be sent. + * @param delay If greater than 0, will delay the sending of the next message. + * @param forceExtended Whether or not to force use of an extended ID. + * + * @return int32_t The total amount of bytes sent. + */ + ssize_t CanDriver::sendMessageQueue(queue& messages, nanoseconds delay, bool forceExtended) { if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } ssize_t totalBytesWritten = 0; @@ -203,6 +242,10 @@ namespace sockcanpp { while (!messages.empty()) { totalBytesWritten += sendMessage(messages.front(), forceExtended); messages.pop(); + + if (delay.count() > 0) { + sleep_for(delay); + } } return totalBytesWritten; @@ -232,9 +275,7 @@ namespace sockcanpp { * @param mask The bit mask to apply. * @param filterId The ID to filter on. */ - void CanDriver::setCanFilterMask(const int32_t mask, const CanId& filterId) { - setCanFilters({{filterId, static_cast(mask)}}); - } + void CanDriver::setCanFilterMask(const int32_t mask, const CanId& filterId) { setCanFilters({{filterId, static_cast(mask)}}); } /** * @brief Sets multiple CAN filters for the associated CAN bus. @@ -262,6 +303,21 @@ namespace sockcanpp { throw CanInitException(formatString("FAILED to set CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); } } + + /** + * @brief Sets the error filter for the associated CAN bus. + * + * @param enabled Whether or not to enable the error filter. + */ + void CanDriver::setErrorFilter(const bool enabled/* = true*/) const { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + int32_t errorFilter = enabled ? 1 : 0; + + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &errorFilter, sizeof(errorFilter)) == -1) { + throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + } + } #pragma endregion ////////////////////////////////////// @@ -274,15 +330,10 @@ namespace sockcanpp { * @brief Initialises the underlying CAN socket. */ void CanDriver::initialiseSocketCan() { - // unique_lock locky(_lock); - - struct sockaddr_can address; - struct ifreq ifaceRequest; - int64_t fdOptions = 0; - int32_t tmpReturn; - - memset(&address, 0, sizeof(struct sockaddr_can)); - memset(&ifaceRequest, 0, sizeof(struct ifreq)); + struct sockaddr_can address{}; + struct ifreq ifaceRequest{}; + int64_t fdOptions{0}; + int32_t tmpReturn{0}; _socketFd = socket(PF_CAN, SOCK_RAW, _canProtocol); @@ -290,7 +341,7 @@ namespace sockcanpp { throw CanInitException(formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno))); } - strcpy(ifaceRequest.ifr_name, _canInterface.c_str()); + std::copy(_canInterface.begin(), _canInterface.end(), ifaceRequest.ifr_name); if ((tmpReturn = ioctl(_socketFd, SIOCGIFINDEX, &ifaceRequest)) == -1) { throw CanInitException(formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", _canInterface.c_str(), errno, diff --git a/test/app/src/Main.cpp b/test/app/src/Main.cpp index fcb2445..a44f2fe 100644 --- a/test/app/src/Main.cpp +++ b/test/app/src/Main.cpp @@ -30,6 +30,8 @@ #include #include +using namespace std::chrono_literals; + using sockcanpp::CanDriver; using sockcanpp::CanId; using sockcanpp::exceptions::CanException; @@ -87,7 +89,7 @@ int main(int32_t argCount, char** argValues) { catch (InvalidSocketException& ex) { cerr << "Failed to send test message! " << ex.what() << endl; } printf("Reading messages\n"); - if (!canDriver->waitForMessages()) continue; + if (!canDriver->waitForMessages(3000ns)) continue; cout << "Reading queue..." << endl; auto canMessages = canDriver->readQueuedMessages(); From 0c7a807fabec1afdaa4bef124fb84bc28566c795 Mon Sep 17 00:00:00 2001 From: SimonC Date: Mon, 24 Mar 2025 14:56:13 +0100 Subject: [PATCH 10/29] Fixed regression which caused higher-than-expected timeouts. (#24) * Potentially fixed a timeout bug * Potentially fixed a timeout bug * Bumped up version --------- Co-authored-by: Simon Cahill --- CMakeLists.txt | 2 +- src/CanDriver.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0f7747b..73de7d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.4.0) +project(sockcanpp LANGUAGES CXX VERSION 1.4.1) option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 59e5d75..576a108 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -106,8 +106,7 @@ namespace sockcanpp { unique_lock locky(_lock); fd_set readFileDescriptors; - timeval waitTime; - waitTime.tv_usec = timeout.count(); + timeval waitTime{0, static_cast(timeout.count())}; FD_ZERO(&readFileDescriptors); FD_SET(_socketFd, &readFileDescriptors); From 2023763bd7c9f56f1e70310e542384eefb0836a7 Mon Sep 17 00:00:00 2001 From: SimonC Date: Tue, 25 Mar 2025 14:35:31 +0100 Subject: [PATCH 11/29] Bug fix & add support for joining filters. (#25) * Added option to join CAN filters on socket level. * Bumped up version * Fixed bug which invalidated the CAN_INV_FILTER flag. * Added more bitwise assignment operators. * Removed accidental member * Began adding support for CAN XL, CAN FD and some other CAN options. * Updated copyright * CanXL can only be enabled, if certain flags are available. --------- Co-authored-by: Simon Cahill --- CMakeLists.txt | 2 +- include/CanDriver.hpp | 13 ++++--- include/CanId.hpp | 13 +++++-- include/CanMessage.hpp | 12 +++---- src/CanDriver.cpp | 79 ++++++++++++++++++++++++++++++++++++++---- 5 files changed, 97 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 73de7d7..25d86b9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.4.1) +project(sockcanpp LANGUAGES CXX VERSION 1.4.2) option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index de28ea1..f935c10 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -5,9 +5,7 @@ * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -106,9 +104,16 @@ namespace sockcanpp { virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus + public: // +++ Socket Management +++ + virtual void allowCanFdFrames(const bool enabled = true) const; //!< Sets the CAN FD frame option for the interface + #ifdef CANXL_XLF + virtual void allowCanXlFrames(const bool enabled = true) const; //!< Sets the CAN XL frame option for the interface + #endif // CANXL_XLF + virtual void joinCanFilters() const; //!< Configures the socket to join the CAN filters virtual void setCanFilterMask(const int32_t mask, const CanId& filterId); //!< Attempts to set a new CAN filter mask to the interface virtual void setCanFilters(const filtermap_t& filters); //!< Sets the CAN filters for the interface virtual void setErrorFilter(const bool enabled = true) const; //!< Sets the error filter for the interface + virtual void setReceiveOwnMessages(const bool enabled = true) const; //!< Sets the receive own messages option for the interface protected: // +++ Socket Management +++ virtual void initialiseSocketCan(); //!< Initialises socketcan @@ -135,8 +140,6 @@ namespace sockcanpp { }; - - /** * @brief Formats a std string object. * diff --git a/include/CanId.hpp b/include/CanId.hpp index c924a49..ad529f5 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -5,9 +5,7 @@ * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 Simon Cahill - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -152,6 +150,15 @@ namespace sockcanpp { #endif // __cpp_concepts >= 201907 constexpr CanId operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. + + template + constexpr CanId operator |=(const T x) { return m_identifier |= x; } //!< Performs a bitwise OR operation on this ID and another. + + template + constexpr CanId operator &=(const T x) { return m_identifier &= x; } //!< Performs a bitwise AND operation on this ID and another. + + template + constexpr CanId operator ^=(const T x) { return m_identifier ^= x; } //!< Performs a bitwise XOR operation on this ID and another. #pragma endregion #pragma region "Arithmetic Operators" diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index 0825173..7e6782e 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -5,9 +5,7 @@ * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 Simon Cahill - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,14 +55,14 @@ namespace sockcanpp { CanMessage(const struct can_frame frame): _canIdentifier(frame.can_id), _frameData((const char*)frame.data, frame.can_dlc), _rawFrame(frame) { } - CanMessage(const CanId canId, const string frameData): _canIdentifier(canId), _frameData(frameData) { - if (frameData.size() > 8) { + CanMessage(const CanId canId, const string& frameData): _canIdentifier(canId), _frameData(frameData) { + if (frameData.size() > CAN_MAX_DLEN) { throw system_error(error_code(0xbadd1c, generic_category()), "Payload too big!"); } - struct can_frame rawFrame; + struct can_frame rawFrame{}; rawFrame.can_id = canId; - memcpy(rawFrame.data, frameData.data(), frameData.size()); + std::copy(frameData.begin(), frameData.end(), rawFrame.data); rawFrame.can_dlc = frameData.size(); _rawFrame = rawFrame; diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 576a108..8eca87f 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -5,10 +5,7 @@ * @version 0.1 * @date 2020-07-01 * - * @copyright Copyright (c) 2020 Simon Cahill - * - * - * Copyright 2020 Simon Cahill + * @copyright Copyright (c) 2020-2025 Simon Cahill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -268,6 +265,59 @@ namespace sockcanpp { return messages; } + /** + * @brief Sets the CAN FD frame option for the interface. + * + * This option allows the current driver instance to receive CAN FD frames. + * + * @param enabled Whether or not to enable the CAN FD frame option. + */ + void CanDriver::allowCanFdFrames(const bool enabled/* = true*/) const { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + int32_t canFdFrames = enabled ? 1 : 0; + + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canFdFrames, sizeof(canFdFrames)) == -1) { + throw CanInitException(formatString("FAILED to set CAN FD frames on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + } + } + + #ifdef CANXL_XLF + /** + * @brief Sets the CAN XL option for the interface. + * + * This option allows the current driver instance to send/receive CAN XL frames. + * + * @param enabled Whether or not to enable the CAN XL option. + */ + void CanDriver::allowCanXlFrames(const bool enabled/* = true*/) const { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + int32_t canXlFrames = enabled ? 1 : 0; + + + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canXlFrames, sizeof(canXlFrames)) == -1) { + throw CanInitException(formatString("FAILED to set CAN XL frames on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + } + } + #endif // CANXL_XLF + + /** + * @brief Configures the socket to join the CAN filters. + * This is especially required, when using inverted CAN filters. + * + * Source: https://stackoverflow.com/a/57680496/2921426 + */ + void CanDriver::joinCanFilters() const { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + int32_t joinFilters = 1; + + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS, &joinFilters, sizeof(joinFilters)) == -1) { + throw CanInitException(formatString("FAILED to join CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + } + } + /** * @brief Attempts to set the filter mask for the associated CAN bus. * @@ -290,11 +340,11 @@ namespace sockcanpp { // Structured bindings only available with C++17 #if __cplusplus >= 201703L for (const auto [id, filter] : filters) { - canFilters.push_back({id, filter}); + canFilters.push_back({*id, filter}); } #else for (const auto& filterPair : filters) { - canFilters.push_back({filterPair.first, filterPair.second}); + canFilters.push_back({*filterPair.first, filterPair.second}); } #endif @@ -317,6 +367,23 @@ namespace sockcanpp { throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); } } + + /** + * @brief Sets the receive own messages option for the associated CAN bus. + * + * This option allows the socket to receive its own messages. + * + * @param enabled Whether or not to enable the receive own messages option. + */ + void CanDriver::setReceiveOwnMessages(const bool enabled) const { + if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + + int32_t receiveOwnMessages = enabled ? 1 : 0; + + if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &receiveOwnMessages, sizeof(receiveOwnMessages)) == -1) { + throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + } + } #pragma endregion ////////////////////////////////////// From bcce87bc234aec3ce4deee758953664da2d0fc85 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Sun, 30 Mar 2025 14:43:10 +0100 Subject: [PATCH 12/29] Add exception headers to the installable headers. (#26) (#27) --- include/CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 95d9c4a..c4858a4 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -7,6 +7,10 @@ target_sources(${PROJECT_NAME} CanDriver.hpp CanId.hpp CanMessage.hpp + exceptions/CanCloseException.hpp + exceptions/CanException.hpp + exceptions/CanInitException.hpp + exceptions/InvalidSocketException.hpp ) if (TARGET sockcanpp_test) @@ -17,5 +21,9 @@ if (TARGET sockcanpp_test) CanDriver.hpp CanId.hpp CanMessage.hpp + exceptions/CanCloseException.hpp + exceptions/CanException.hpp + exceptions/CanInitException.hpp + exceptions/InvalidSocketException.hpp ) -endif() \ No newline at end of file +endif() From 23d5ac4d76c734e15dcf6df673dd5c10eb8d1f26 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Sun, 30 Mar 2025 15:44:41 +0200 Subject: [PATCH 13/29] Bumped up version --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 25d86b9..3675e0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.4.2) +project(sockcanpp LANGUAGES CXX VERSION 1.4.3) option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) From 61319b93b437442a3f75a5ddffdbd898e4581f97 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 31 Mar 2025 10:11:27 +0100 Subject: [PATCH 14/29] Correct exception what() declarations to const noexcept methods. (#28) --- include/exceptions/CanCloseException.hpp | 4 ++-- include/exceptions/CanException.hpp | 4 ++-- include/exceptions/CanInitException.hpp | 4 ++-- include/exceptions/InvalidSocketException.hpp | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/exceptions/CanCloseException.hpp b/include/exceptions/CanCloseException.hpp index 4bd366f..44fb7ec 100644 --- a/include/exceptions/CanCloseException.hpp +++ b/include/exceptions/CanCloseException.hpp @@ -42,7 +42,7 @@ namespace sockcanpp { namespace exceptions { ~CanCloseException() {} public: // +++ Overrides +++ - const char* what() { return _message.c_str(); } + const char* what() const noexcept { return _message.c_str(); } private: string _message; @@ -50,4 +50,4 @@ namespace sockcanpp { namespace exceptions { } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANCLOSEEXCEPTION_HPP diff --git a/include/exceptions/CanException.hpp b/include/exceptions/CanException.hpp index ba545d6..4a7d11b 100644 --- a/include/exceptions/CanException.hpp +++ b/include/exceptions/CanException.hpp @@ -42,7 +42,7 @@ namespace sockcanpp { namespace exceptions { ~CanException() {} public: // +++ Overrides +++ - const char* what() { return m_message.c_str(); } + const char* what() const noexcept { return m_message.c_str(); } public: // +++ Getter +++ const int32_t getSocket() const { return m_socket; } @@ -55,4 +55,4 @@ namespace sockcanpp { namespace exceptions { } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANEXCEPTION_HPP diff --git a/include/exceptions/CanInitException.hpp b/include/exceptions/CanInitException.hpp index 443991d..e37bf7a 100644 --- a/include/exceptions/CanInitException.hpp +++ b/include/exceptions/CanInitException.hpp @@ -42,7 +42,7 @@ namespace sockcanpp { namespace exceptions { virtual ~CanInitException() { } public: // +++ Override +++ - const char* what() { return _message.c_str(); } + const char* what() const noexcept { return _message.c_str(); } private: string _message; @@ -50,4 +50,4 @@ namespace sockcanpp { namespace exceptions { } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_CANINITEXCEPTION_HPP diff --git a/include/exceptions/InvalidSocketException.hpp b/include/exceptions/InvalidSocketException.hpp index 5fd2c9d..fb658c3 100644 --- a/include/exceptions/InvalidSocketException.hpp +++ b/include/exceptions/InvalidSocketException.hpp @@ -42,7 +42,7 @@ namespace sockcanpp { namespace exceptions { ~InvalidSocketException() {} public: // +++ Overrides +++ - const char* what() { return m_message.c_str(); } + const char* what() const noexcept { return m_message.c_str(); } public: // +++ Getter +++ const int32_t getSocket() const { return m_socket; } @@ -55,4 +55,4 @@ namespace sockcanpp { namespace exceptions { } /* exceptions */ } /* sockcanpp */ -#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP \ No newline at end of file +#endif // LIBSOCKCANPP_INCLUDE_EXCEPTIONS_INVALIDSOCKETEXCEPTION_HPP From b9d1be620fe1ef06fd94c036b0f5955f53355b29 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Mon, 31 Mar 2025 11:18:42 +0200 Subject: [PATCH 15/29] Bumped up version --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3675e0f..3408b5f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.4.3) +project(sockcanpp LANGUAGES CXX VERSION 1.4.4) option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) @@ -98,4 +98,4 @@ add_custom_command( if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND BUILD_TESTS STREQUAL "ON") enable_testing() add_subdirectory(test) -endif() \ No newline at end of file +endif() From 6b99892c98e79121a173dd6beab841632f9ef5c6 Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 31 Mar 2025 17:37:33 +0100 Subject: [PATCH 16/29] In readQueuedMessages() support interfaces like vcan that don't support FIONREAD. (#30) --- include/CanDriver.hpp | 1 + src/CanDriver.cpp | 26 ++++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index f935c10..57c696b 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -131,6 +131,7 @@ namespace sockcanpp { int32_t _canProtocol; //!< The protocol used when communicating via CAN int32_t _socketFd{-1}; //!< The CAN socket file descriptor int32_t _queueSize{0}; ///!< The size of the message queue read by waitForMessages() + bool _canReadQueueSize{true}; ///!< Is the queue size available //!< Mutex for thread-safety. mutex _lock{}; diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 8eca87f..a6d6308 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -115,6 +115,9 @@ namespace sockcanpp { _queueSize = static_cast(std::ceil(bytesAvailable / sizeof(can_frame))); } else { _queueSize = 0; + // vcan interfaces don't support FIONREAD. So fall back to + // using alternate implementation in readQueuedMessages(). + _canReadQueueSize = false; } return fdsAvailable > 0; @@ -258,8 +261,27 @@ namespace sockcanpp { unique_lock locky{_lock}; queue messages{}; - for (int32_t i = _queueSize; 0 < i; --i) { - messages.emplace(readMessageLock(false)); + if (_canReadQueueSize) { + for (int32_t i = _queueSize; 0 < i; --i) { + messages.emplace(readMessageLock(false)); + } + } else { + // If the interface doesn't support FIONREAD, fall back + // to reading until data exhausted. + bool more{true}; + + do { + ssize_t readBytes; + can_frame canFrame{}; + readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); + if (readBytes >= 0) { + messages.emplace(canFrame); + } else if (errno == EAGAIN) { + more = false; + } else { + throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); + } + } while (more); } return messages; From 400554acbdfd194224a819ae23520c8731d84e0f Mon Sep 17 00:00:00 2001 From: Jim Hague Date: Mon, 31 Mar 2025 17:38:19 +0100 Subject: [PATCH 17/29] Observe -protocol argument in app test. (#29) --- test/app/src/Main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/app/src/Main.cpp b/test/app/src/Main.cpp index a44f2fe..47b7ad3 100644 --- a/test/app/src/Main.cpp +++ b/test/app/src/Main.cpp @@ -75,7 +75,7 @@ int main(int32_t argCount, char** argValues) { CanDriver* canDriver{nullptr}; try { - canDriver = new CanDriver(canInterface, CAN_RAW); + canDriver = new CanDriver(canInterface, desiredCanSocket); } catch (CanInitException& ex) { cerr << "An error occurred while initialising CanDriver: " << ex.what() << endl; if (canDriver) { delete canDriver; } From b9062dfa50be9d6ddf066872aadf360741b43501 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Mon, 31 Mar 2025 18:40:20 +0200 Subject: [PATCH 18/29] Bumped up minor version to reflect significant improvements to the codebase. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3408b5f..652b688 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.4.4) +project(sockcanpp LANGUAGES CXX VERSION 1.5.0) option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) From 79add8d52f627324274a38152a8b4d81de875cfc Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Mon, 31 Mar 2025 18:43:46 +0200 Subject: [PATCH 19/29] Removed extra call in CMakeLists which caused pipeline to fail --- CMakeLists.txt | 8 -------- 1 file changed, 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 652b688..611ba14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,11 +91,3 @@ add_custom_command( COMMAND doxygen ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} ) - -### -# If the CMAKE_BUILD_TYPE is set to Debug, enable the tests -### -if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND BUILD_TESTS STREQUAL "ON") - enable_testing() - add_subdirectory(test) -endif() From 5a72d13be3bffea231bd2cd0480dd827b35fa7d3 Mon Sep 17 00:00:00 2001 From: Simon Cahill Date: Mon, 14 Apr 2025 19:02:08 +0200 Subject: [PATCH 20/29] Added funding config --- FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 FUNDING.yml diff --git a/FUNDING.yml b/FUNDING.yml new file mode 100644 index 0000000..33f095f --- /dev/null +++ b/FUNDING.yml @@ -0,0 +1 @@ +github: SimonCahill From e2513b9c3163000b8e44bdcec4d607cae27ddf09 Mon Sep 17 00:00:00 2001 From: SimonC Date: Sat, 26 Jul 2025 21:51:21 +0200 Subject: [PATCH 21/29] Added support for default constructors in CanMessage. (#31) * Added support for default constructors in CanMessage. Destructor is now marked as default. * Updated version, added project description and homepage URL. --- CMakeLists.txt | 2 +- include/CanMessage.hpp | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 611ba14..a4e794d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.5.0) +project(sockcanpp LANGUAGES CXX VERSION 1.5.1 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index 7e6782e..de40525 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -52,10 +52,12 @@ namespace sockcanpp { */ class CanMessage { public: // +++ Constructor / Destructor +++ - CanMessage(const struct can_frame frame): + CanMessage() = default; + + explicit CanMessage(const struct can_frame frame): _canIdentifier(frame.can_id), _frameData((const char*)frame.data, frame.can_dlc), _rawFrame(frame) { } - CanMessage(const CanId canId, const string& frameData): _canIdentifier(canId), _frameData(frameData) { + explicit CanMessage(const CanId canId, const string& frameData): _canIdentifier(canId), _frameData(frameData) { if (frameData.size() > CAN_MAX_DLEN) { throw system_error(error_code(0xbadd1c, generic_category()), "Payload too big!"); } @@ -68,7 +70,7 @@ namespace sockcanpp { _rawFrame = rawFrame; } - virtual ~CanMessage() {} + virtual ~CanMessage() = default; public: // +++ Getters +++ const CanId getCanId() const { return this->_canIdentifier; } From 41add373544a38dfa52e33e6ae407a7c661898cb Mon Sep 17 00:00:00 2001 From: SimonC Date: Tue, 5 Aug 2025 15:48:50 +0200 Subject: [PATCH 22/29] Refactored code; added timestamps for incoming CAN frames. (#32) * Refactored CanMessage class. * Bumped up version * Experimenting with gen-AI for README generation. * Refactored CanId struct. Fixed borked include guard. Fixed assignment. * Refactored CanMessage. Includes timestamps per message. Added static assert for can_frame. Added helper functions for ID validation. Added span and string_view for your data viewing pleasure. CanMessage is now CanMessageT, a template class. An alias has been added for backward-compat. * Refactored CanDriver class. All member variables are now prefixed with `m_` instead of just `_`. m_canProtocol is now default initialised. Removed extra slashes from Doxy comments. Added function to enable telemetry data (timestamps). These are currently implemented as an `ioctl`. Perhaps I'll implement them as cmsgs. Queues are now passed by value. * Added missing reference operator. * Reverted back change where message queue were passed by value. * Fixed error caused by Copilot's stupid auto-insertion BS. * Added some tests for the CanMessage type. * Apparently I completely misunderstood the way std::span works and have refactored the class. * Fixed test execution. * Added new GitHub workflows to run builds, packs and tests without concept support (C++11) * Restored compatibility with C++11. * Renamed GitHub workflows to better distinguish them from the C++20 counterparts. * Update src/CanDriver.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/CanDriver.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/CanDriver.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fixed preprocessor condition where `formatString` was falsely used when `std::format` is available. C&P error. * Removed `isValid` check entirely. I need to check what Copilot does more carefully. * If telemetry collection is enabled, it is now also collected when reading multiple messages * Added C++11 support for std:: * Added support for relative timestamps * Fixed stupid bug found by LLVM on an embedded platform * Experimenting with linker settings; clang doesn't like having in its compiler flags * Added CodeQL check --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/cmake_no_concepts.yml | 42 + .github/workflows/codeql.yml | 100 ++ .github/workflows/cpack_no_concepts.yml | 48 + .github/workflows/gtest_no_concepts.yml | 48 + CMakeLists.txt | 9 +- README.md | 163 +- include/CanDriver.hpp | 88 +- include/CanId.hpp | 44 +- include/CanMessage.hpp | 127 +- include/TlOptional.hpp | 2062 +++++++++++++++++++++++ src/CanDriver.cpp | 259 ++- test/unit/src/CanMessage_Tests.cpp | 61 + 12 files changed, 2843 insertions(+), 208 deletions(-) create mode 100644 .github/workflows/cmake_no_concepts.yml create mode 100644 .github/workflows/codeql.yml create mode 100644 .github/workflows/cpack_no_concepts.yml create mode 100644 .github/workflows/gtest_no_concepts.yml create mode 100644 include/TlOptional.hpp create mode 100644 test/unit/src/CanMessage_Tests.cpp diff --git a/.github/workflows/cmake_no_concepts.yml b/.github/workflows/cmake_no_concepts.yml new file mode 100644 index 0000000..bf6d792 --- /dev/null +++ b/.github/workflows/cmake_no_concepts.yml @@ -0,0 +1,42 @@ +name: Build Debug (C++11) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dsockcanpp_CONCEPT_SUPPORT=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..41af201 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,100 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ ] + schedule: + - cron: '34 4 * * 0' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: c-cpp + build-mode: autobuild + # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Add any setup steps before running the `github/codeql-action/init` action. + # This includes steps like installing compilers or runtimes (`actions/setup-node` + # or others). This is typically only required for manual builds. + # - name: Setup runtime (example) + # uses: actions/setup-example@v1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/cpack_no_concepts.yml b/.github/workflows/cpack_no_concepts.yml new file mode 100644 index 0000000..5c785ed --- /dev/null +++ b/.github/workflows/cpack_no_concepts.yml @@ -0,0 +1,48 @@ +name: Build and Pack (C++11) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -Dsockcanpp_CONCEPT_SUPPORT=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Pack + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: cpack . + diff --git a/.github/workflows/gtest_no_concepts.yml b/.github/workflows/gtest_no_concepts.yml new file mode 100644 index 0000000..8131a9b --- /dev/null +++ b/.github/workflows/gtest_no_concepts.yml @@ -0,0 +1,48 @@ +name: Build and Test (C++11) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt -y install can-utils libsocketcan-dev googletest + while read -r cmd + do + eval sudo $cmd + done < <(Rscript -e 'writeLines(remotes::system_requirements("ubuntu", "20.04"))') + + - uses: actions/checkout@v3 + - uses: MarkusJx/googletest-installer@v1.1 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=ON -Dsockcanpp_CONCEPT_SUPPORT=OFF + + - name: Build + # Build your program with the given configuration + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test + working-directory: ${{github.workspace}}/build + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + run: ctest -C ${{env.BUILD_TYPE}} + diff --git a/CMakeLists.txt b/CMakeLists.txt index a4e794d..138e558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.5.1 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.6.0 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) @@ -24,7 +24,11 @@ add_compile_options( ) set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g") -set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -s") +set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2") + +if (CMAKE_BUILD_TYPE STREQUAL "Release") + add_link_options($<$:-s>) +endif() include(GNUInstallDirs) @@ -40,6 +44,7 @@ endif() # If BUILD_TESTS is set to ON, a static test library with the name of the project suffixed with "_test" will be created ### if(BUILD_TESTS STREQUAL "ON") + enable_testing() add_library(${PROJECT_NAME}_test STATIC) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/test/) endif() diff --git a/README.md b/README.md index da98905..4f3cc0e 100644 --- a/README.md +++ b/README.md @@ -49,122 +49,137 @@ target_link_libraries( 3) generate and build 4) ??? profit -## Using the CAN driver +**Disclaimer: This next section was generated by AI. I hope I didn't miss any major errors**. -libsockcanpp provides a simple interface to socketcan, which is represented by the CanDriver class.
-`CanDriver` handles the setup of the socket; **it does not however setup the CAN interface!** -### Instantiating CanDriver +# 📘 CanDriver Usage Guide (with CanId) -The example below describes how to instantiate a new instance of CanDriver.
-You can create as many instances as required, the resources are `free`'d once the instance has gone out of scope or been deleted. +This guide provides a high-level overview and usage examples for the `CanDriver` class in the `libsockcanpp` C++ library. It includes basic setup, message transmission and reception, filter configuration, and integration of `CanId`. -The following parameters are required for correct instantiation: +--- -1) CAN interface: [v]can[0-?] -2) CAN protocol: see [linux/can.h](https://github.com/linux-can/can-utils/blob/master/include/linux/can.h) for options -3) (optional) CAN sender ID (arbitration ID) - -The following exceptions may be thrown if something went wrong during initialisation: - - - @see CanInitException - - if socketcan failed to initlialise - - if ioctl failed - - if socket binding failed - -CanDriver is fully thread-safe and can be used in multi-threaded applications. +## 🧰 Basic Setup ```cpp -#include +#include "CanDriver.hpp" -using sockcanpp::CanDriver; +using namespace sockcanpp; int main() { - CanDriver canDriver("can0", CAN_RAW[, 0xbad]); + // Construct CanDriver instance for vcan0 with default settings + CanDriver driver("vcan0", CAN_RAW); + + // Optionally enable telemetry (e.g., timestamped messages) + driver.setCollectTelemetry(true); return 0; } ``` -### Using CAN IDs +--- -libsockcanpp provides a first-class datatype, @see CanId, which acts as an integer which can be either 11 or 29 bits in size.
-The @see CanId type is used through libsockcanpp to provide a semi fool-proof method of using CAN arbitration IDs without the pitfalls of using traditional 32-bit integers. +## 📥 Receiving Messages -CanId supports the following operations: - - bitwise AND/OR - - casting to: [u]int16, [u]int32 - - basic comparison: - - equal to - - not equal to - - greater than (or equal to) - - less than (or equal to) - - arithmetic: - - addition - - subtraction +```cpp +// Wait for messages (blocking, 3ms timeout) +if (driver.waitForMessages(std::chrono::milliseconds(3))) { + CanMessage msg = driver.readMessage(); -### Sending CAN frames + std::cout << "Received ID: " << msg.getCanId().toString() + << " Data: " << msg.getFrameData() << std::endl; +} +``` -Sending CAN frames with sockcanpp is as easy as you could imagine. +--- -1) instantiate a @see CanDriver object -2) create a @see CanMessage -3) send message +## 📤 Sending Messages ```cpp -#include +CanId id(0x123); +CanMessage msg(id, "Hello"); -using sockcanpp::CanDriver; -using sockcanpp::CanId; -using sockcanpp::CanMessage; +// Send using extended frame (optional) +driver.sendMessage(msg, /* forceExtended = */ true); +``` -void sendCanFrameExample() { - CanDriver canDriver("can0", CAN_RAW[, 0xd00d]); +--- - CanMessage messageToSend(0 /*send with default ID*/, "8 bytes!" /* the data */); +## 📦 Queueing Messages - auto sentByteCount = canDriver.sendMessage(messageToSend[, false /* don't force extended ID */]); +```cpp +std::queue queue; +queue.push(CanMessage(CanId(0x100), "A")); +queue.push(CanMessage(CanId(0x101), "B")); + +driver.sendMessageQueue(queue, std::chrono::milliseconds(10)); +``` + +--- + +## 🎯 Using CanId + +```cpp +CanId id1(0x1A0); +CanId id2("0x1A1"); // Requires C++ concepts - printf("Sent %d bytes via CAN!\n", sentByteCount); +// Comparison +if (id1 < id2) { + std::cout << "id1 is less than id2" << std::endl; } -void sendMultipleFramesExample() { - CanDriver canDriver("can1", CAN_RAW[, 0 /* no default ID */]); +// Masking (for filtering) +uint32_t mask = 0x7F0; // Match only the upper bits +driver.setCanFilterMask(mask, id1); +``` - queue messageQueue = { - CanMessage(0x269, "somedata"), - Canmessage(0x1e9, "moredata") - }; +--- - auto sentByteCount = canDriver.sendMessageQueue(messageQueue[, milliseconds(20) /* delay between frames */[, false /* don't force extended ID */]]); +## 🎚️ Configuring Filters - printf("Sent %d bytes via CAN!\n", sentByteCount); +```cpp +sockcanpp::filtermap_t filters = { + { CanId(0x100), 0x7FF }, + { CanId(0x200), 0x700 } +}; -} +driver.setCanFilters(filters); ``` -### Receiving messages via CAN +--- + +## 🚀 Advanced Features + +### Enable CAN FD Frames + +```cpp +driver.allowCanFdFrames(true); +``` -Receiving CAN messages is almost as simple as sending them! Firstly, check if there are any messages in the buffer, then pull them out; either one-by-one, or all at once! +### Enable CAN XL Frames (if supported) ```cpp -#include +driver.allowCanXlFrames(true); +``` -using sockcanpp::CanDriver; -using sockcanpp::CanId; -using sockcanpp::CanMessage; +### Receive Own Transmissions -void receiveCanFramesExample() { - CanDriver canDriver("can2", CAN_RAW[, 0 /* no default ID */]); +```cpp +driver.setReceiveOwnMessages(true); +``` - if (canDriver.waitForMessages([milliseconds(3000) /* timeout */])) { - // read a single message - CanMessage receivedMessage = canDriver.readMessage(); +--- - // read all available messages - queue receivedMessages = canDriver.readQueuedMessages(); +## 🧪 Reading All Queued Messages - // handle CAN frames - } +```cpp +auto allMessages = driver.readQueuedMessages(); +while (!allMessages.empty()) { + const auto& msg = allMessages.front(); + std::cout << msg.getCanId().toString() << ": " << msg.getFrameData() << std::endl; + allMessages.pop(); } ``` + +--- + +© 2020–2025 Simon Cahill — Licensed under Apache License 2.0 diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index 57c696b..89e6ee0 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -32,12 +32,20 @@ #include #include +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + ////////////////////////////// // LOCAL INCLUDES // ////////////////////////////// #include "CanId.hpp" #include "CanMessage.hpp" +#if __cplusplus < 201703L +#include "TlOptional.hpp" +#endif // __cplusplus < 201703L + /** * @brief Main library namespace. * @@ -45,7 +53,15 @@ */ namespace sockcanpp { + #if __cplusplus >= 201300 using namespace std::chrono_literals; + #endif // __cplusplus >= 201300 + + #if __cplusplus >= 201703L + using std::optional; + #else + using tl::optional; + #endif // __cplusplus < 201703L using std::chrono::microseconds; using std::chrono::milliseconds; @@ -79,29 +95,47 @@ namespace sockcanpp { virtual ~CanDriver() { uninitialiseSocketCan(); } //!< Destructor public: // +++ Getter / Setter +++ - CanDriver& setDefaultSenderId(const CanId& id) { this->_defaultSenderId = id; return *this; } //!< Sets the default sender ID + CanDriver& setDefaultSenderId(const CanId& id) { this->m_defaultSenderId = id; return *this; } //!< Sets the default sender ID - CanId getDefaultSenderId() const { return this->_defaultSenderId; } //!< Gets the default sender ID + CanId getDefaultSenderId() const { return this->m_defaultSenderId; } //!< Gets the default sender ID - filtermap_t getFilterMask() const { return this->_canFilterMask; } //!< Gets the filter mask used by this instance + filtermap_t getFilterMask() const { return this->m_canFilterMask; } //!< Gets the filter mask used by this instance - int32_t getMessageQueueSize() const { return this->_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() - int32_t getSocketFd() const { return this->_socketFd; } //!< The socket file descriptor used by this instance. + int32_t getMessageQueueSize() const { return this->m_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() + int32_t getSocketFd() const { return this->m_socketFd; } //!< The socket file descriptor used by this instance. - string getCanInterface() const { return this->_canInterface; } //!< The CAN interface used by this instance. + string getCanInterface() const { return this->m_canInterface; } //!< The CAN interface used by this instance. public: // +++ I/O +++ - virtual bool waitForMessages(microseconds timeout = 3000us); //!< Waits for CAN messages to appear - virtual bool waitForMessages(milliseconds timeout = 3000ms); //!< Waits for CAN messages to appear - virtual bool waitForMessages(nanoseconds timeout = 3000ns); //!< Waits for CAN messages to appear + #if __cplusplus >= 201300 + #define sockcanpp_3KUS 3000us + #define sockcanpp_3KMS 3000ms + #define sockcanpp_3KNS 3000ns + + #define sockcanpp_20US 20us + #define sockcanpp_20MS 20ms + #define sockcanpp_20NS 20ns + #else // C++11 + #define sockcanpp_3KUS std::chrono::microseconds(3000) + #define sockcanpp_3KMS std::chrono::milliseconds(3000) + #define sockcanpp_3KNS std::chrono::nanoseconds(3000) + + #define sockcanpp_20US std::chrono::microseconds(20) + #define sockcanpp_20MS std::chrono::milliseconds(20) + #define sockcanpp_20NS std::chrono::nanoseconds(20) + #endif // __cplusplus >= 201300 + + virtual bool waitForMessages(microseconds timeout = sockcanpp_3KUS); //!< Waits for CAN messages to appear + virtual bool waitForMessages(milliseconds timeout = sockcanpp_3KMS); //!< Waits for CAN messages to appear + virtual bool waitForMessages(nanoseconds timeout = sockcanpp_3KNS); //!< Waits for CAN messages to appear virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message - virtual ssize_t sendMessageQueue(queue& messages, microseconds delay = 20us, bool forceExtended = false); //!< Attempts to send a queue of messages - virtual ssize_t sendMessageQueue(queue& messages, milliseconds delay = 20ms, bool forceExtended = false); //!< Attempts to send a queue of messages - virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay = 20ns, bool forceExtended = false); //!< Attempts to send a queue of messages - + virtual ssize_t sendMessageQueue(queue& messages, microseconds delay = sockcanpp_20US, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, milliseconds delay = sockcanpp_20MS, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay = sockcanpp_20NS, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus public: // +++ Socket Management +++ @@ -112,8 +146,10 @@ namespace sockcanpp { virtual void joinCanFilters() const; //!< Configures the socket to join the CAN filters virtual void setCanFilterMask(const int32_t mask, const CanId& filterId); //!< Attempts to set a new CAN filter mask to the interface virtual void setCanFilters(const filtermap_t& filters); //!< Sets the CAN filters for the interface + virtual void setCollectTelemetry(const bool enabled = true); //!< Sets the telemetry collection option for the interface virtual void setErrorFilter(const bool enabled = true) const; //!< Sets the error filter for the interface virtual void setReceiveOwnMessages(const bool enabled = true) const; //!< Sets the receive own messages option for the interface + virtual void setReturnRelativeTimestamps(const bool enabled = true) { m_relativeTimestamps = enabled; } protected: // +++ Socket Management +++ virtual void initialiseSocketCan(); //!< Initialises socketcan @@ -121,23 +157,27 @@ namespace sockcanpp { private: // +++ Member Functions +++ virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard + virtual milliseconds readFrameTimestamp(); private: // +++ Variables +++ - - CanId _defaultSenderId; //!< The ID to send messages with if no other ID was set. + bool m_canReadQueueSize{true}; //!< Is the queue size available + bool m_collectTelemetry{false}; //!< Whether or not to collect telemetry data from the CAN bus + bool m_relativeTimestamps{false}; //!< Whether or not to use relative timestamps + + CanId m_defaultSenderId; //!< The ID to send messages with if no other ID was set. - filtermap_t _canFilterMask; //!< The bit mask used to filter CAN messages + filtermap_t m_canFilterMask; //!< The bit mask used to filter CAN messages - int32_t _canProtocol; //!< The protocol used when communicating via CAN - int32_t _socketFd{-1}; //!< The CAN socket file descriptor - int32_t _queueSize{0}; ///!< The size of the message queue read by waitForMessages() - bool _canReadQueueSize{true}; ///!< Is the queue size available + int32_t m_canProtocol{CAN_RAW}; //!< The protocol used when communicating via CAN + int32_t m_socketFd{-1}; //!< The CAN socket file descriptor + int32_t m_queueSize{0}; //!< The size of the message queue read by waitForMessages() - //!< Mutex for thread-safety. - mutex _lock{}; - mutex _lockSend{}; + optional m_firstTimestamp{}; + + mutex m_lock{}; //!< Mutex for thread-safety. + mutex m_lockSend{}; - string _canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) + string m_canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) }; diff --git a/include/CanId.hpp b/include/CanId.hpp index ad529f5..efae908 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -20,8 +20,8 @@ * limitations under the License. */ -#ifndef LIBSOCKPP_INCLUDE_CANID_HPP -#define LIBSOCKPP_INCLUDE_CANID_HPP +#ifndef LIBSOCKCANPP_INCLUDE_CANID_HPP +#define LIBSOCKCANPP_INCLUDE_CANID_HPP ////////////////////////////// // SYSTEM INCLUDES // @@ -132,33 +132,55 @@ namespace sockcanpp { constexpr bool operator >(T x) const { return static_cast(x) > m_identifier; } //!< Compares this ID to a 32-bit integer. template - constexpr bool operator <=(const T x) const { return x.m_identifier <= m_identifier; } //!< Compares this ID to another. + constexpr bool operator <=(const T x) const { return m_identifier <= static_cast(x); } //!< Compares this ID to another. template - constexpr bool operator >=(const T x) const { return x.m_identifier >= m_identifier; } //!< Compares this ID to another. + constexpr bool operator >=(const T x) const { return m_identifier >= static_cast(x); } //!< Compares this ID to another. #pragma endregion #pragma region "Assignment Operators" + #if __cplusplus >= 201703L template - constexpr CanId operator =(const T val) { return CanId(val); } //!< Assigns a new integer to this CanID + constexpr CanId& operator =(const T val) { m_identifier = val; return *this; } //!< Assigns a new integer to this CanID + #else + template + CanId& operator =(const T val) { + m_identifier = static_cast(val); + return *this; + } //!< Assigns a new integer to this CanID + #endif // __cplusplus >= 201703L #if __cpp_concepts >= 201907 template - CanId operator =(const T& val) { + CanId& operator =(const T& val) { return operator=(std::stoul(val.data(), nullptr, 16)); } #endif // __cpp_concepts >= 201907 - constexpr CanId operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. + + #if __cplusplus >= 201703L + constexpr CanId& operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. + + template + constexpr CanId& operator |=(const T x) { m_identifier |= x; return *this; } //!< Performs a bitwise OR operation on this ID and another. + + template + constexpr CanId& operator &=(const T x) { m_identifier &= x; return *this; } //!< Performs a bitwise AND operation on this ID and another. + + template + constexpr CanId& operator ^=(const T x) { m_identifier ^= x; return *this; } //!< Performs a bitwise XOR operation on this ID and another. + #else + CanId& operator =(const int64_t val) { return operator =((canid_t)val); } //!< Assigns a 64-bit integer to this ID. template - constexpr CanId operator |=(const T x) { return m_identifier |= x; } //!< Performs a bitwise OR operation on this ID and another. + CanId& operator |=(const T x) { m_identifier |= x; return *this; } //!< Performs a bitwise OR operation on this ID and another. template - constexpr CanId operator &=(const T x) { return m_identifier &= x; } //!< Performs a bitwise AND operation on this ID and another. + CanId& operator &=(const T x) { m_identifier &= x; return *this; } //!< Performs a bitwise AND operation on this ID and another. template - constexpr CanId operator ^=(const T x) { return m_identifier ^= x; } //!< Performs a bitwise XOR operation on this ID and another. + CanId& operator ^=(const T x) { m_identifier ^= x; return *this; } //!< Performs a bitwise XOR operation on this ID and another. + #endif // __cplusplus >= 201703L #pragma endregion #pragma region "Arithmetic Operators" @@ -269,4 +291,4 @@ namespace sockcanpp { } -#endif // LIBSOCKPP_INCLUDE_CANID_HPP +#endif // LIBSOCKCANPP_INCLUDE_CANID_HPP diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index de40525..11fbc5a 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -28,8 +28,12 @@ ////////////////////////////// #include +#include #include #include +#if __cpp_lib_span >= 201907L +#include +#endif // __cpp_lib_span >= 201907L #include #include #include @@ -41,50 +45,131 @@ namespace sockcanpp { + #if __cpp_lib_bit_cast >= 201806L + # define type_cast std::bit_cast + #else + # define type_cast reinterpret_cast + #endif // __cpp_lib_bit_cast >= 201806L + + using std::chrono::milliseconds; using std::error_code; using std::generic_category; - using std::memcpy; using std::string; using std::system_error; + #if __cpp_lib_string_view >= 201803L + using std::string_view; //!< Use string_view if available, otherwise use string. + #endif // __cpp_lib_string_view + /** * @brief Represents a CAN message that was received. */ - class CanMessage { + template + class CanMessageT { + static_assert(std::is_trivially_copyable::value, "can_frame must be trivially copyable"); //!< Ensures that the can_frame structure is trivially copyable, which is necessary for efficient copying and moving of CAN messages. + public: // +++ Constructor / Destructor +++ - CanMessage() = default; + CanMessageT() = default; //!< Default constructor, initializes an empty CAN message. + + explicit CanMessageT(const struct can_frame& frame): m_canIdentifier(frame.can_id), m_rawFrame(frame) { } //!< Constructs a CAN message from a raw can_frame structure. + explicit CanMessageT(const struct can_frame& frame, const Duration& timestampOffset): m_canIdentifier(frame.can_id), m_timestampOffset(timestampOffset), m_rawFrame(frame) { } //!< Constructs a CAN message from a raw can_frame structure with a timestamp offset. + + /** + * @brief Constructs a CAN message from a CAN ID and a frame data string. + * + * @param canId The CAN ID of the message. + * @param frameData The data of the CAN frame as a string. + */ + explicit CanMessageT(const CanId& canId, const string& frameData): m_canIdentifier(canId) { + if (frameData.size() > CAN_MAX_DLEN) { + throw system_error(std::make_error_code(std::errc::message_size), "Payload too big!"); + } - explicit CanMessage(const struct can_frame frame): - _canIdentifier(frame.can_id), _frameData((const char*)frame.data, frame.can_dlc), _rawFrame(frame) { } + m_rawFrame.can_id = canId; + std::copy(frameData.begin(), frameData.end(), m_rawFrame.data); + m_rawFrame.can_dlc = frameData.size(); + } - explicit CanMessage(const CanId canId, const string& frameData): _canIdentifier(canId), _frameData(frameData) { + explicit CanMessageT(const CanId& canId, const string& frameData, const Duration& timestampOffset): CanMessageT(canId, frameData) { + m_timestampOffset = timestampOffset; + } //!< Constructs a CAN message from a CAN ID and a frame data string with a timestamp offset. + + #if __cpp_lib_span >= 201907L + /** + * @brief Constructs a CAN message from a CAN ID and a span of bytes. + * + * @param canId The CAN ID of the message. + * @param frameData The data of the CAN frame as a span of bytes. + */ + explicit CanMessageT(const CanId& canId, const std::span& frameData): m_canIdentifier(canId) { if (frameData.size() > CAN_MAX_DLEN) { - throw system_error(error_code(0xbadd1c, generic_category()), "Payload too big!"); + throw system_error(std::make_error_code(std::errc::message_size), "Payload too big!"); } - struct can_frame rawFrame{}; - rawFrame.can_id = canId; - std::copy(frameData.begin(), frameData.end(), rawFrame.data); - rawFrame.can_dlc = frameData.size(); - - _rawFrame = rawFrame; + m_rawFrame.can_id = canId; + std::copy(frameData.begin(), frameData.end(), m_rawFrame.data); + m_rawFrame.len = frameData.size(); } - virtual ~CanMessage() = default; + explicit CanMessageT(const CanId& canId, const std::span& frameData, const Duration& timestampOffset): CanMessageT(canId, frameData) { + m_timestampOffset = timestampOffset; + } //!< Constructs a CAN message from a CAN ID and a span of bytes with a timestamp offset. + #endif // __cpp_lib_span >= 201907L + + CanMessageT(const CanMessageT& other) = default; //!< Copy constructor. + CanMessageT(CanMessageT&& other) noexcept = default; //!< Move constructor. + CanMessageT& operator=(const CanMessageT& other) = default; //!< Copy assignment operator. + CanMessageT& operator=(CanMessageT&& other) noexcept = default; //!< Move assignment operator. + + virtual ~ CanMessageT() = default; + + public: // +++ Public API +++ + const CanId& getCanId() const noexcept { return m_canIdentifier; } //!< Returns the CAN ID of this message. - public: // +++ Getters +++ - const CanId getCanId() const { return this->_canIdentifier; } - const string getFrameData() const { return this->_frameData; } - const can_frame getRawFrame() const { return this->_rawFrame; } + const string getFrameData() const noexcept { + string data{}; + data.reserve(m_rawFrame.len); + std::copy(std::begin(m_rawFrame.data), std::begin(m_rawFrame.data) + m_rawFrame.can_dlc, std::back_inserter(data)); + + return data; + } //!< Returns the frame data as a string. + + const can_frame& getRawFrame() const noexcept { return m_rawFrame; } //!< Returns the raw can_frame structure of this message. + + const Duration& getTimestampOffset() const noexcept { return m_timestampOffset; } //!< Returns the timestamp offset of this message. + + [[nodiscard]] constexpr bool isErrorFrame() const noexcept { return m_canIdentifier.hasErrorFrameFlag(); } //!< Checks if the CAN message is an error frame. + + [[nodiscard]] constexpr bool isRemoteTransmissionRequest() const noexcept { return m_canIdentifier.hasRtrFrameFlag(); } //!< Checks if the CAN message is a remote transmission request. + + [[nodiscard]] constexpr bool isStandardFrameId() const noexcept { return m_canIdentifier.isStandardFrameId(); } //!< Checks if the CAN message has a standard frame ID. + + [[nodiscard]] constexpr bool isExtendedFrameId() const noexcept { return m_canIdentifier.isExtendedFrameId(); } //!< Checks if the CAN message has an extended frame ID. + + public: // +++ Equality Checks +++ + bool operator==(const CanMessageT& other) const noexcept { + return m_canIdentifier == other.m_canIdentifier && + m_rawFrame.can_dlc == other.m_rawFrame.can_dlc && + std::equal( + std::begin(m_rawFrame.data), std::begin(m_rawFrame.data) + m_rawFrame.can_dlc, + std::begin(other.m_rawFrame.data), std::begin(other.m_rawFrame.data) + other.m_rawFrame.can_dlc + ); + } //!< Compares this CAN message to another for equality. + + bool operator!=(const CanMessageT& other) const noexcept { + return !(*this == other); + } //!< Compares this CAN message to another for inequality. private: - CanId _canIdentifier; + CanId m_canIdentifier{}; - string _frameData; + Duration m_timestampOffset{}; - struct can_frame _rawFrame; + struct can_frame m_rawFrame{}; }; + using CanMessage = CanMessageT; //!< Default CAN message type with milliseconds as the timestamp offset. + } #endif // LIBSOCKCANPP_INCLUDE_CANMESSAGE_HPP diff --git a/include/TlOptional.hpp b/include/TlOptional.hpp new file mode 100644 index 0000000..2b48c45 --- /dev/null +++ b/include/TlOptional.hpp @@ -0,0 +1,2062 @@ + +/// +// optional - An implementation of std::optional with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_OPTIONAL_HPP +#define TL_OPTIONAL_HPP + +#define TL_OPTIONAL_VERSION_MAJOR 1 +#define TL_OPTIONAL_VERSION_MINOR 1 +#define TL_OPTIONAL_VERSION_PATCH 0 + +#include +#include +#include +#include +#include + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_OPTIONAL_MSVC2015 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +#define TL_OPTIONAL_NO_CONSTRR + +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && \ + !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { + namespace detail { + template + struct is_trivially_copy_constructible : std::is_trivially_copy_constructible{}; +#ifdef _GLIBCXX_VECTOR + template + struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible{}; +#endif + } +} +#endif + +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#else +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#endif + +#if __cplusplus > 201103L +#define TL_OPTIONAL_CXX14 +#endif + +// constexpr implies const in C++11, not C++14 +#if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ + defined(TL_OPTIONAL_GCC49)) +#define TL_OPTIONAL_11_CONSTEXPR +#else +#define TL_OPTIONAL_11_CONSTEXPR constexpr +#endif + +namespace tl { +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +/// Used to represent an optional with no data; essentially a bool +class monostate {}; + +/// A tag type to tell optional to construct its value in-place +struct in_place_t { + explicit in_place_t() = default; +}; +/// A tag to tell optional to construct its value in-place +static constexpr in_place_t in_place{}; +#endif + +template class optional; + +namespace detail { +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template struct is_pointer_to_non_const_member_func : std::false_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; +template +struct is_pointer_to_non_const_member_func : std::true_type{}; + +template struct is_const_or_const_ref : std::false_type{}; +template struct is_const_or_const_ref : std::true_type{}; +template struct is_const_or_const_ref : std::true_type{}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template ::value + && is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, + int = 0> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> +constexpr auto invoke(Fn &&f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template tag swap(T &, T &); +template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + &&detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { +}; +#endif +#endif + +// std::void_t from C++17 +template struct voider { using type = void; }; +template using void_t = typename voider::type; + +// Trait for checking if a type is a tl::optional +template struct is_optional_impl : std::false_type {}; +template struct is_optional_impl> : std::true_type {}; +template using is_optional = is_optional_impl>; + +// Change void to tl::monostate +template +using fixup_void = conditional_t::value, monostate, U>; + +template > +using get_map_return = optional>>; + +// Check if invoking F for some Us returns void +template struct returns_void_impl; +template +struct returns_void_impl>, U...> + : std::is_void> {}; +template +using returns_void = returns_void_impl; + +template +using enable_if_ret_void = enable_if_t::value>; + +template +using disable_if_ret_void = enable_if_t::value>; + +template +using enable_forward_value = + detail::enable_if_t::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using enable_from_other = detail::enable_if_t< + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using enable_assign_forward = detail::enable_if_t< + !std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && std::is_assignable::value>; + +template +using enable_assign_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_assignable::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value && + !std::is_assignable &>::value && + !std::is_assignable &&>::value>; + +// The storage base manages the actual storage, and correctly propagates +// trivial destruction from T. This case is for when T is not trivially +// destructible. +template ::value> +struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + ~optional_storage_base() { + if (m_has_value) { + m_value.~T(); + m_has_value = false; + } + } + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value; +}; + +// This case is for when T is trivially destructible. +template struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + // No destructor, so this class is trivially destructible + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value = false; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template struct optional_operations_base : optional_storage_base { + using optional_storage_base::optional_storage_base; + + void hard_reset() noexcept { + get().~T(); + this->m_has_value = false; + } + + template void construct(Args &&... args) { + new (std::addressof(this->m_value)) T(std::forward(args)...); + this->m_has_value = true; + } + + template void assign(Opt &&rhs) { + if (this->has_value()) { + if (rhs.has_value()) { + this->m_value = std::forward(rhs).get(); + } else { + this->m_value.~T(); + this->m_has_value = false; + } + } + + else if (rhs.has_value()) { + construct(std::forward(rhs).get()); + } + } + + bool has_value() const { return this->m_has_value; } + + TL_OPTIONAL_11_CONSTEXPR T &get() & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR const T &get() const & { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR T &&get() && { return std::move(this->m_value); } +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_value); } +#endif +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T is trivially copy constructible +template +struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; +}; + +// This specialization is for when T is not trivially copy constructible +template +struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + + optional_copy_base() = default; + optional_copy_base(const optional_copy_base &rhs) + : optional_operations_base() { + if (rhs.has_value()) { + this->construct(rhs.get()); + } else { + this->m_has_value = false; + } + } + + optional_copy_base(optional_copy_base &&rhs) = default; + optional_copy_base &operator=(const optional_copy_base &rhs) = default; + optional_copy_base &operator=(optional_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_OPTIONAL_GCC49 +template ::value> +struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; +}; +#else +template struct optional_move_base; +#endif +template struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + + optional_move_base() = default; + optional_move_base(const optional_move_base &rhs) = default; + + optional_move_base(optional_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) { + if (rhs.has_value()) { + this->construct(std::move(rhs.get())); + } else { + this->m_has_value = false; + } + } + optional_move_base &operator=(const optional_move_base &rhs) = default; + optional_move_base &operator=(optional_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template +struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; +}; + +template +struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + + optional_copy_assign_base() = default; + optional_copy_assign_base(const optional_copy_assign_base &rhs) = default; + + optional_copy_assign_base(optional_copy_assign_base &&rhs) = default; + optional_copy_assign_base &operator=(const optional_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + optional_copy_assign_base & + operator=(optional_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_OPTIONAL_GCC49 +template ::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; +}; +#else +template struct optional_move_assign_base; +#endif + +template +struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + + optional_move_assign_base() = default; + optional_move_assign_base(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base(optional_move_assign_base &&rhs) = default; + + optional_move_assign_base & + operator=(const optional_move_assign_base &rhs) = default; + + optional_move_assign_base & + operator=(optional_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// optional_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value, + bool EnableMove = std::is_move_constructible::value> +struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = default; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base &) = delete; + optional_delete_ctor_base(optional_delete_ctor_base &&) noexcept = delete; + optional_delete_ctor_base & + operator=(const optional_delete_ctor_base &) = default; + optional_delete_ctor_base & + operator=(optional_delete_ctor_base &&) noexcept = default; +}; + +// optional_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible + assignable +template ::value && + std::is_copy_assignable::value), + bool EnableMove = (std::is_move_constructible::value && + std::is_move_assignable::value)> +struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = default; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = default; +}; + +template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = default; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = delete; +}; + +template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = delete; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = default; +}; + +template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base &) = default; + optional_delete_assign_base(optional_delete_assign_base &&) noexcept = + default; + optional_delete_assign_base & + operator=(const optional_delete_assign_base &) = delete; + optional_delete_assign_base & + operator=(optional_delete_assign_base &&) noexcept = delete; +}; + +} // namespace detail + +/// A tag type to represent an empty optional +struct nullopt_t { + struct do_not_use {}; + constexpr explicit nullopt_t(do_not_use, do_not_use) noexcept {} +}; +/// Represents an empty optional +static constexpr nullopt_t nullopt{nullopt_t::do_not_use{}, + nullopt_t::do_not_use{}}; + +class bad_optional_access : public std::exception { +public: + bad_optional_access() = default; + const char *what() const noexcept { return "Optional has no value"; } +}; + +/// An optional object is an object that contains the storage for another +/// object and manages the lifetime of this contained object, if any. The +/// contained object may be initialized after the optional object has been +/// initialized, and may be destroyed before the optional object has been +/// destroyed. The initialization state of the contained object is tracked by +/// the optional object. +template +class optional : private detail::optional_move_assign_base, + private detail::optional_delete_ctor_base, + private detail::optional_delete_assign_base { + using base = detail::optional_move_assign_base; + + static_assert(!std::is_same::value, + "instantiation of optional with in_place_t is ill-formed"); + static_assert(!std::is_same, nullopt_t>::value, + "instantiation of optional with nullopt_t is ill-formed"); + +public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const & { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) & { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) && { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const & { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const && { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u`. + template U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept = default; + + constexpr optional(nullopt_t) noexcept {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value in-place using the given arguments. + template + constexpr explicit optional( + detail::enable_if_t::value, in_place_t>, + Args &&... args) + : base(in_place, std::forward(args)...) {} + + template + TL_OPTIONAL_11_CONSTEXPR explicit optional( + detail::enable_if_t &, + Args &&...>::value, + in_place_t>, + std::initializer_list il, Args &&... args) { + this->construct(il, std::forward(args)...); + } + + /// Constructs the stored value with `u`. + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr optional(U &&u) : base(in_place, std::forward(u)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::enable_forward_value * = nullptr> + constexpr explicit optional(U &&u) : base(in_place, std::forward(u)) {} + + /// Converting copy constructor. + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + template * = nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit optional(const optional &rhs) { + if (rhs.has_value()) { + this->construct(*rhs); + } + } + + /// Converting move constructor. + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + template < + class U, detail::enable_from_other * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit optional(optional &&rhs) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + /// Destroys the stored value if there is one. + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + + return *this; + } + + /// Copy assignment. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Move assignment. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional &operator=(optional &&rhs) = default; + + /// Assigns the stored value from `u`, destroying the old value if there was + /// one. + template * = nullptr> + optional &operator=(U &&u) { + if (has_value()) { + this->m_value = std::forward(u); + } else { + this->construct(std::forward(u)); + } + + return *this; + } + + /// Converting copy assignment operator. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(const optional &rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = *rhs; + } else { + this->hard_reset(); + } + } + + else if (rhs.has_value()) { + this->construct(*rhs); + } + + return *this; + } + + // TODO check exception guarantee + /// Converting move assignment operator. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional &operator=(optional &&rhs) { + if (has_value()) { + if (rhs.has_value()) { + this->m_value = std::move(*rhs); + } else { + this->hard_reset(); + } + } + + else if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } + + return *this; + } + + /// Constructs the value in-place, destroying the current one if there is + /// one. + template T &emplace(Args &&... args) { + static_assert(std::is_constructible::value, + "T must be constructible with Args"); + + *this = nullopt; + this->construct(std::forward(args)...); + return value(); + } + + template + detail::enable_if_t< + std::is_constructible &, Args &&...>::value, + T &> + emplace(std::initializer_list il, Args &&... args) { + *this = nullopt; + this->construct(il, std::forward(args)...); + return value(); + } + + /// Swaps this optional with the other. + /// + /// If neither optionals have a value, nothing happens. + /// If both have a value, the values are swapped. + /// If one has a value, it is moved to the other and the movee is left + /// valueless. + void + swap(optional &rhs) noexcept(std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + using std::swap; + if (has_value()) { + if (rhs.has_value()) { + swap(**this, *rhs); + } else { + new (std::addressof(rhs.m_value)) T(std::move(this->m_value)); + this->m_value.T::~T(); + } + } else if (rhs.has_value()) { + new (std::addressof(this->m_value)) T(std::move(rhs.m_value)); + rhs.m_value.T::~T(); + } + swap(this->m_has_value, rhs.m_has_value); + } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const { + return std::addressof(this->m_value); + } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() { + return std::addressof(this->m_value); + } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() & { return this->m_value; } + + constexpr const T &operator*() const & { return this->m_value; } + + TL_OPTIONAL_11_CONSTEXPR T &&operator*() && { + return std::move(this->m_value); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T &&operator*() const && { return std::move(this->m_value); } +#endif + + /// Returns whether or not the optional has a value + constexpr bool has_value() const noexcept { return this->m_has_value; } + + constexpr explicit operator bool() const noexcept { + return this->m_has_value; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() & { + if (has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const & { + if (has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR T &&value() && { + if (has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + TL_OPTIONAL_11_CONSTEXPR const T &&value() const && { + if (has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } +#endif + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U &&u) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? std::move(**this) : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { + if (has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + } +}; // namespace tl + +/// Compares two optional objects +template +inline constexpr bool operator==(const optional &lhs, + const optional &rhs) { + return lhs.has_value() == rhs.has_value() && + (!lhs.has_value() || *lhs == *rhs); +} +template +inline constexpr bool operator!=(const optional &lhs, + const optional &rhs) { + return lhs.has_value() != rhs.has_value() || + (lhs.has_value() && *lhs != *rhs); +} +template +inline constexpr bool operator<(const optional &lhs, + const optional &rhs) { + return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); +} +template +inline constexpr bool operator>(const optional &lhs, + const optional &rhs) { + return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); +} +template +inline constexpr bool operator<=(const optional &lhs, + const optional &rhs) { + return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); +} +template +inline constexpr bool operator>=(const optional &lhs, + const optional &rhs) { + return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); +} + +/// Compares an optional to a `nullopt` +template +inline constexpr bool operator==(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); +} +template +inline constexpr bool operator==(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); +} +template +inline constexpr bool operator!=(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); +} +template +inline constexpr bool operator!=(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); +} +template +inline constexpr bool operator<(const optional &, nullopt_t) noexcept { + return false; +} +template +inline constexpr bool operator<(nullopt_t, const optional &rhs) noexcept { + return rhs.has_value(); +} +template +inline constexpr bool operator<=(const optional &lhs, nullopt_t) noexcept { + return !lhs.has_value(); +} +template +inline constexpr bool operator<=(nullopt_t, const optional &) noexcept { + return true; +} +template +inline constexpr bool operator>(const optional &lhs, nullopt_t) noexcept { + return lhs.has_value(); +} +template +inline constexpr bool operator>(nullopt_t, const optional &) noexcept { + return false; +} +template +inline constexpr bool operator>=(const optional &, nullopt_t) noexcept { + return true; +} +template +inline constexpr bool operator>=(nullopt_t, const optional &rhs) noexcept { + return !rhs.has_value(); +} + +/// Compares the optional with a value. +template +inline constexpr bool operator==(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs == rhs : false; +} +template +inline constexpr bool operator==(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs == *rhs : false; +} +template +inline constexpr bool operator!=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs != rhs : true; +} +template +inline constexpr bool operator!=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs != *rhs : true; +} +template +inline constexpr bool operator<(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs < rhs : true; +} +template +inline constexpr bool operator<(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs < *rhs : false; +} +template +inline constexpr bool operator<=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs <= rhs : true; +} +template +inline constexpr bool operator<=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs <= *rhs : false; +} +template +inline constexpr bool operator>(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs > rhs : false; +} +template +inline constexpr bool operator>(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs > *rhs : true; +} +template +inline constexpr bool operator>=(const optional &lhs, const U &rhs) { + return lhs.has_value() ? *lhs >= rhs : false; +} +template +inline constexpr bool operator>=(const U &lhs, const optional &rhs) { + return rhs.has_value() ? lhs >= *rhs : true; +} + +template ::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> +void swap(optional &lhs, + optional &rhs) noexcept(noexcept(lhs.swap(rhs))) { + return lhs.swap(rhs); +} + +namespace detail { +struct i_am_secret {}; +} // namespace detail + +template ::value, + detail::decay_t, T>> +inline constexpr optional make_optional(U &&v) { + return optional(std::forward(v)); +} + +template +inline constexpr optional make_optional(Args &&... args) { + return optional(in_place, std::forward(args)...); +} +template +inline constexpr optional make_optional(std::initializer_list il, + Args &&... args) { + return optional(in_place, il, std::forward(args)...); +} + +#if __cplusplus >= 201703L +template optional(T)->optional; +#endif + +/// \exclude +namespace detail { +#ifdef TL_OPTIONAL_CXX14 +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto optional_map_impl(Opt &&opt, F &&f) { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto optional_map_impl(Opt &&opt, F &&f) { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return make_optional(monostate{}); + } + + return optional(nullopt); +} +#else +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto optional_map_impl(Opt &&opt, F &&f) -> optional { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); +} + +template (), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto optional_map_impl(Opt &&opt, F &&f) -> optional { + if (opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return nullopt; +} +#endif +} // namespace detail + +/// Specialization for when `T` is a reference. `optional` acts similarly +/// to a `T*`, but provides more operations and shows intent more clearly. +template class optional { +public: +// The different versions for C++14 and 11 are needed because deduced return +// types are not SFINAE-safe. This provides better support for things like +// generic lambdas. C.f. +// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template constexpr auto and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F &&f) && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F &&f) const & { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F &&f) const && { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) & { + return detail::optional_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template auto transform(F &&f) &&; + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const & { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const && { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) & { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) && { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F &&f) const & { + if (has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F &&f) const & { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F &&f) const && { + if (has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F &&f) const && { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u` + template U map_or(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F &&f, U &&u) & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const & { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F &&f, U &&u) const && { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U &&u) const { + using result = optional>; + return has_value() ? result{u} : result{nullopt}; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) & { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional &rhs) const & { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional &rhs) && { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional &rhs) const && { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) & { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional &&rhs) const & { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional &&rhs) && { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional &&rhs) const && { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T &; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept : m_value(nullptr) {} + + constexpr optional(nullopt_t) noexcept : m_value(nullptr) {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional &rhs) noexcept = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional &&rhs) = default; + + /// Constructs the stored value with `u`. + template >::value> + * = nullptr> + constexpr optional(U &&u) noexcept : m_value(std::addressof(u)) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + } + + template + constexpr explicit optional(const optional &rhs) noexcept : optional(*rhs) {} + + /// No-op + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional &operator=(nullopt_t) noexcept { + m_value = nullptr; + return *this; + } + + /// Copy assignment. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + optional &operator=(const optional &rhs) = default; + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional &operator=(U &&u) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + m_value = std::addressof(u); + return *this; + } + + /// Converting copy assignment operator. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + template optional &operator=(const optional &rhs) noexcept { + m_value = std::addressof(rhs.value()); + return *this; + } + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional &emplace(U &&u) noexcept { + return *this = std::forward(u); + } + + void swap(optional &rhs) noexcept { std::swap(m_value, rhs.m_value); } + + /// Returns a pointer to the stored value + constexpr const T *operator->() const noexcept { return m_value; } + + TL_OPTIONAL_11_CONSTEXPR T *operator->() noexcept { return m_value; } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T &operator*() noexcept { return *m_value; } + + constexpr const T &operator*() const noexcept { return *m_value; } + + constexpr bool has_value() const noexcept { return m_value != nullptr; } + + constexpr explicit operator bool() const noexcept { + return m_value != nullptr; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T &value() { + if (has_value()) + return *m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T &value() const { + if (has_value()) + return *m_value; + throw bad_optional_access(); + } + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U &&u) const & noexcept { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// \group value_or + template TL_OPTIONAL_11_CONSTEXPR T value_or(U &&u) && noexcept { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { m_value = nullptr; } + +private: + T *m_value; +}; // namespace tl + + + +} // namespace tl + +namespace std { +// TODO SFINAE +template struct hash> { + ::std::size_t operator()(const tl::optional &o) const { + if (!o.has_value()) + return 0; + + return std::hash>()(*o); + } +}; +} // namespace std + +#endif \ No newline at end of file diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index a6d6308..82d5122 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -63,13 +64,13 @@ namespace sockcanpp { using std::mutex; using std::queue; using std::string; - using std::strncpy; using std::unique_lock; using std::unique_ptr; using std::chrono::milliseconds; - using std::this_thread::sleep_for; using std::vector; + namespace thread = std::this_thread; + ////////////////////////////////////// // PUBLIC IMPLEMENTATION // ////////////////////////////////////// @@ -83,7 +84,7 @@ namespace sockcanpp { CanDriver(canInterface, canProtocol, filtermap_t{{0, filterMask}}, defaultSenderId) { } CanDriver::CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId): - _defaultSenderId(defaultSenderId), _canFilterMask(filters), _canProtocol(canProtocol), _canInterface(canInterface) { + m_defaultSenderId(defaultSenderId), m_canFilterMask(filters), m_canProtocol(canProtocol), m_canInterface(canInterface) { initialiseSocketCan(); } #pragma endregion @@ -98,26 +99,26 @@ namespace sockcanpp { * @return false Otherwise. */ bool CanDriver::waitForMessages(microseconds timeout/* = microseconds(3000)*/) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky(_lock); + unique_lock locky(m_lock); fd_set readFileDescriptors; timeval waitTime{0, static_cast(timeout.count())}; FD_ZERO(&readFileDescriptors); - FD_SET(_socketFd, &readFileDescriptors); - const auto fdsAvailable = select(_socketFd + 1, &readFileDescriptors, nullptr, nullptr, &waitTime); + FD_SET(m_socketFd, &readFileDescriptors); + const auto fdsAvailable = select(m_socketFd + 1, &readFileDescriptors, nullptr, nullptr, &waitTime); int32_t bytesAvailable{0}; - const auto retCode = ioctl(_socketFd, FIONREAD, &bytesAvailable); + const auto retCode = ioctl(m_socketFd, FIONREAD, &bytesAvailable); if (retCode == 0) { - _queueSize = static_cast(std::ceil(bytesAvailable / sizeof(can_frame))); + m_queueSize = static_cast(std::ceil(bytesAvailable / sizeof(can_frame))); } else { - _queueSize = 0; + m_queueSize = 0; // vcan interfaces don't support FIONREAD. So fall back to // using alternate implementation in readQueuedMessages(). - _canReadQueueSize = false; + m_canReadQueueSize = false; } return fdsAvailable > 0; @@ -155,22 +156,57 @@ namespace sockcanpp { * * @return CanMessage The message read from the bus. */ - CanMessage CanDriver::readMessageLock(bool const lock) { + CanMessage CanDriver::readMessageLock(bool const lock /* = true */) { unique_ptr> locky{nullptr}; - if (lock) { locky = unique_ptr>{new unique_lock{_lock}}; } + if (lock) { locky = unique_ptr>{new unique_lock{m_lock}}; } - if (0 > _socketFd) { throw InvalidSocketException("Invalid socket!", _socketFd); } - - ssize_t readBytes{0}; + if (0 > m_socketFd) { throw InvalidSocketException("Invalid socket!", m_socketFd); } can_frame canFrame{}; - readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); + if (read(m_socketFd, &canFrame, sizeof(can_frame)) < 0) { + throw CanException( + #if __cpp_lib_format < 202002L + formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to read from CAN! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + , m_socketFd); + } - if (0 > readBytes) { throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); } + if (m_collectTelemetry) { + // Read timestamp from the socket if available. + return CanMessage{canFrame, readFrameTimestamp()}; + } return CanMessage{canFrame}; } + + milliseconds CanDriver::readFrameTimestamp() { + struct timeval tv{}; + if (ioctl(m_socketFd, SIOCGSTAMP, &tv) < 0) { + throw CanException( + #if __cpp_lib_format < 202002L + formatString("FAILED to read timestamp from socket! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to read timestamp from socket! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + , m_socketFd); + } + + if (!m_relativeTimestamps) { + return milliseconds(tv.tv_sec * 1000 + tv.tv_usec / 1000); + } + + // If relative timestamps are enabled, either return the delta between now and the first timestamp, or set the first timestamp + if (m_firstTimestamp.has_value()) { + auto relativeTime = milliseconds(tv.tv_sec * 1000 + tv.tv_usec / 1000) - m_firstTimestamp.value(); + return relativeTime; + } else { + m_firstTimestamp = milliseconds(tv.tv_sec * 1000 + tv.tv_usec / 1000); + return milliseconds(0); + } + } /** * @brief Attempts to send a CAN message on the associated bus. @@ -181,23 +217,37 @@ namespace sockcanpp { * @return ssize_t The amount of bytes sent on the bus. */ ssize_t CanDriver::sendMessage(const CanMessage& message, bool forceExtended) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky(_lockSend); + unique_lock locky(m_lockSend); ssize_t bytesWritten = 0; if (message.getFrameData().size() > CAN_MAX_DATA_LENGTH) { - throw CanException(formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH), _socketFd); + throw CanException( + #if __cpp_lib_format < 202002L + formatString("INVALID data length! Message must be smaller than %d bytes!", CAN_MAX_DATA_LENGTH) + #else + std::format("INVALID data length! Message must be smaller than {0:d} bytes!", CAN_MAX_DATA_LENGTH) + #endif // __cpp_lib_format < 202002L + , m_socketFd); } auto canFrame = message.getRawFrame(); if (forceExtended || ((uint32_t)message.getCanId() > CAN_SFF_MASK)) { canFrame.can_id |= CAN_EFF_FLAG; } - bytesWritten = write(_socketFd, (const void*)&canFrame, sizeof(canFrame)); + bytesWritten = write(m_socketFd, &canFrame, sizeof(canFrame)); - if (bytesWritten == -1) { throw CanException(formatString("FAILED to write data to socket! Error: %d => %s", errno, strerror(errno)), _socketFd); } + if (bytesWritten < 0) { + throw CanException( + #if __cpp_lib_format < 202002L + formatString("FAILED to write data to socket! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to write data to socket! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + , m_socketFd); + } return bytesWritten; } @@ -234,7 +284,7 @@ namespace sockcanpp { * @return int32_t The total amount of bytes sent. */ ssize_t CanDriver::sendMessageQueue(queue& messages, nanoseconds delay, bool forceExtended) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } ssize_t totalBytesWritten = 0; @@ -243,7 +293,7 @@ namespace sockcanpp { messages.pop(); if (delay.count() > 0) { - sleep_for(delay); + thread::sleep_for(delay); } } @@ -256,13 +306,13 @@ namespace sockcanpp { * @return queue A queue containing the messages read from the bus buffer. */ queue CanDriver::readQueuedMessages() { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky{_lock}; + unique_lock locky{m_lock}; queue messages{}; - if (_canReadQueueSize) { - for (int32_t i = _queueSize; 0 < i; --i) { + if (m_canReadQueueSize) { + for (int32_t i = m_queueSize; 0 < i; --i) { messages.emplace(readMessageLock(false)); } } else { @@ -273,13 +323,18 @@ namespace sockcanpp { do { ssize_t readBytes; can_frame canFrame{}; - readBytes = read(_socketFd, &canFrame, sizeof(can_frame)); + readBytes = read(m_socketFd, &canFrame, sizeof(can_frame)); if (readBytes >= 0) { - messages.emplace(canFrame); - } else if (errno == EAGAIN) { + if (m_collectTelemetry) { + // Read timestamp from the socket if available. + messages.emplace(CanMessage{canFrame, readFrameTimestamp()}); + } else { + messages.emplace(canFrame); + } + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { more = false; } else { - throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), _socketFd); + throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), m_socketFd); } } while (more); } @@ -295,12 +350,12 @@ namespace sockcanpp { * @param enabled Whether or not to enable the CAN FD frame option. */ void CanDriver::allowCanFdFrames(const bool enabled/* = true*/) const { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } int32_t canFdFrames = enabled ? 1 : 0; - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canFdFrames, sizeof(canFdFrames)) == -1) { - throw CanInitException(formatString("FAILED to set CAN FD frames on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &canFdFrames, sizeof(canFdFrames)) == -1) { + throw CanInitException(formatString("FAILED to set CAN FD frames on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); } } @@ -313,13 +368,13 @@ namespace sockcanpp { * @param enabled Whether or not to enable the CAN XL option. */ void CanDriver::allowCanXlFrames(const bool enabled/* = true*/) const { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } int32_t canXlFrames = enabled ? 1 : 0; - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canXlFrames, sizeof(canXlFrames)) == -1) { - throw CanInitException(formatString("FAILED to set CAN XL frames on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_XL_FRAMES, &canXlFrames, sizeof(canXlFrames)) == -1) { + throw CanInitException(formatString("FAILED to set CAN XL frames on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); } } #endif // CANXL_XLF @@ -331,12 +386,12 @@ namespace sockcanpp { * Source: https://stackoverflow.com/a/57680496/2921426 */ void CanDriver::joinCanFilters() const { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } int32_t joinFilters = 1; - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS, &joinFilters, sizeof(joinFilters)) == -1) { - throw CanInitException(formatString("FAILED to join CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_JOIN_FILTERS, &joinFilters, sizeof(joinFilters)) == -1) { + throw CanInitException(formatString("FAILED to join CAN filters on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); } } @@ -354,14 +409,14 @@ namespace sockcanpp { * @param filters A map containing the filters to apply. */ void CanDriver::setCanFilters(const filtermap_t& filters) { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } - unique_lock locky(_lock); + unique_lock locky(m_lock); vector canFilters{}; // Structured bindings only available with C++17 #if __cplusplus >= 201703L - for (const auto [id, filter] : filters) { + for (const auto& [id, filter] : filters) { canFilters.push_back({*id, filter}); } #else @@ -370,23 +425,36 @@ namespace sockcanpp { } #endif - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, canFilters.data(), canFilters.size() * sizeof(can_filter)) == -1) { - throw CanInitException(formatString("FAILED to set CAN filters on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, canFilters.data(), canFilters.size() * sizeof(can_filter)) == -1) { + throw CanInitException(formatString("FAILED to set CAN filters on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno))); } } + /** + * @brief Enables collection of advanced telemetry for the associated CAN bus. + * + * @param enabled Whether or not to enable telemetry collection. + */ + void CanDriver::setCollectTelemetry(const bool enabled/* = true*/) { m_collectTelemetry = enabled; } + /** * @brief Sets the error filter for the associated CAN bus. * * @param enabled Whether or not to enable the error filter. */ void CanDriver::setErrorFilter(const bool enabled/* = true*/) const { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } - - int32_t errorFilter = enabled ? 1 : 0; - - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &errorFilter, sizeof(errorFilter)) == -1) { - throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } + + can_err_mask_t errorMask{enabled ? CAN_ERR_MASK : 0x00}; + + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_ERR_FILTER, &errorMask, sizeof(can_err_mask_t)) == -1) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno)) + #else + std::format("FAILED to set CAN error filter on socket {0:d}! Error: {1:d} => {2:s}", m_socketFd, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } } @@ -398,12 +466,18 @@ namespace sockcanpp { * @param enabled Whether or not to enable the receive own messages option. */ void CanDriver::setReceiveOwnMessages(const bool enabled) const { - if (_socketFd < 0) { throw InvalidSocketException("Invalid socket!", _socketFd); } + if (m_socketFd < 0) { throw InvalidSocketException("Invalid socket!", m_socketFd); } int32_t receiveOwnMessages = enabled ? 1 : 0; - if (setsockopt(_socketFd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &receiveOwnMessages, sizeof(receiveOwnMessages)) == -1) { - throw CanInitException(formatString("FAILED to set CAN error filter on socket %d! Error: %d => %s", _socketFd, errno, strerror(errno))); + if (setsockopt(m_socketFd, SOL_CAN_RAW, CAN_RAW_RECV_OWN_MSGS, &receiveOwnMessages, sizeof(receiveOwnMessages)) == -1) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to set CAN message echo on socket %d! Error: %d => %s", m_socketFd, errno, strerror(errno)) + #else + std::format("FAILED to set CAN message echo on socket {0:d}! Error: {1:d} => {2:s}", m_socketFd, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } } #pragma endregion @@ -421,32 +495,57 @@ namespace sockcanpp { struct sockaddr_can address{}; struct ifreq ifaceRequest{}; int64_t fdOptions{0}; - int32_t tmpReturn{0}; - _socketFd = socket(PF_CAN, SOCK_RAW, _canProtocol); + m_socketFd = socket(PF_CAN, SOCK_RAW, m_canProtocol); - if (_socketFd == -1) { - throw CanInitException(formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno))); + if (m_socketFd == -1) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to initialise socketcan! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to initialise socketcan! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } - std::copy(_canInterface.begin(), _canInterface.end(), ifaceRequest.ifr_name); - - if ((tmpReturn = ioctl(_socketFd, SIOCGIFINDEX, &ifaceRequest)) == -1) { - throw CanInitException(formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", _canInterface.c_str(), errno, - strerror(errno))); + std::copy(m_canInterface.begin(), m_canInterface.end(), ifaceRequest.ifr_name); + + if (ioctl(m_socketFd, SIOCGIFINDEX, &ifaceRequest) < 0) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to perform IO control operation on socket %s! Error: %d => %s", m_canInterface.c_str(), errno, + strerror(errno)) + #else + std::format("FAILED to perform IO control operation on socket {0:s}! Error: {1:d} => {2:s}", m_canInterface, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } - fdOptions = fcntl(_socketFd, F_GETFL); + fdOptions = fcntl(m_socketFd, F_GETFL); fdOptions |= O_NONBLOCK; - tmpReturn = fcntl(_socketFd, F_SETFL, fdOptions); + if (fcntl(m_socketFd, F_SETFL, fdOptions) < 0) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to set non-blocking mode on socket %s! Error: %d => %s", m_canInterface.c_str(), errno, strerror(errno)) + #else + std::format("FAILED to set non-blocking mode on socket {0:s}! Error: {1:d} => {2:s}", m_canInterface, errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); + } address.can_family = AF_CAN; address.can_ifindex = ifaceRequest.ifr_ifindex; - setCanFilters(_canFilterMask); + setCanFilters(m_canFilterMask); - if ((tmpReturn = bind(_socketFd, (struct sockaddr*)&address, sizeof(address))) == -1) { - throw CanInitException(formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno))); + if (bind(m_socketFd, reinterpret_cast(&address), sizeof(address)) < 0) { + throw CanInitException( + #if __cpp_lib_format < 202002L + formatString("FAILED to bind to socket CAN! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to bind to socket CAN! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); } } @@ -454,13 +553,21 @@ namespace sockcanpp { * @brief Closes the underlying CAN socket. */ void CanDriver::uninitialiseSocketCan() { - unique_lock locky(_lock); - - if (_socketFd <= 0) { throw CanCloseException("Cannot close invalid socket!"); } - - if (close(_socketFd) == -1) { throw CanCloseException(formatString("FAILED to close CAN socket! Error: %d => %s", errno, strerror(errno))); } + unique_lock locky(m_lock); + + if (m_socketFd <= 0) { throw CanCloseException("Cannot close invalid socket!"); } + + if (close(m_socketFd) == -1) { + throw CanCloseException( + #if __cpp_lib_format < 202002L + formatString("FAILED to close CAN socket! Error: %d => %s", errno, strerror(errno)) + #else + std::format("FAILED to close CAN socket! Error: {0:d} => {1:s}", errno, strerror(errno)) + #endif // __cpp_lib_format < 202002L + ); + } - _socketFd = -1; + m_socketFd = -1; } #pragma endregion diff --git a/test/unit/src/CanMessage_Tests.cpp b/test/unit/src/CanMessage_Tests.cpp new file mode 100644 index 0000000..772e1ab --- /dev/null +++ b/test/unit/src/CanMessage_Tests.cpp @@ -0,0 +1,61 @@ +/** + * @file CanMessage_Tests.cpp + * @author Simon Cahill (s.cahill@procyon-systems.de) + * @brief Contains all the unit tests for the CanMessage structure. + * @version 0.1 + * @date 2025-07-30 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#include + +#include + +using sockcanpp::CanMessage; + +using std::string; + +TEST(CanMessageTests, CanMessage_Constructor_ExpectDefaultValues) { + CanMessage msg; + + ASSERT_EQ(msg.getCanId(), 0); + ASSERT_EQ(msg.getFrameData(), ""); + ASSERT_TRUE(msg.getRawFrame().can_id == 0 && msg.getRawFrame().can_dlc == 0); +} + +TEST(CanMessageTests, CanMessage_ConstructorWithId_ExpectCorrectId) { + CanMessage msg(0x123, ""); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), ""); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 0); +} + +TEST(CanMessageTests, CanMessage_ConstructorWithId_ExpectCorrectIdAndTestData) { + CanMessage msg(0x123, "TestData"); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), "TestData"); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 8); +} + +TEST(CanMessageTests, CanMessage_ConstructorWithIdAndTimestamp_ExpectCorrectValues) { + auto timestamp = std::chrono::milliseconds(100); + CanMessage msg(0x123, "TestData", timestamp); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), "TestData"); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 8); + ASSERT_EQ(msg.getTimestampOffset(), timestamp); +} + +#if __cpp_lib_span >= 202002L +TEST(CanMessageTests, CanMessage_ConstructorWithIdAndSpan_ExpectCorrectValues) { + CanMessage msg(0x123, std::span(reinterpret_cast("TestData"), 8)); + + ASSERT_EQ(msg.getCanId(), 0x123); + ASSERT_EQ(msg.getFrameData(), "TestData"); + ASSERT_TRUE(msg.getRawFrame().can_id == 0x123 && msg.getRawFrame().can_dlc == 8); +} +#endif // __cpp_lib_span >= 202002L \ No newline at end of file From 085ccb9de6f7ba7096bf59c8bb45b3bca422abf6 Mon Sep 17 00:00:00 2001 From: SimonC Date: Thu, 7 Aug 2025 01:19:50 +0200 Subject: [PATCH 23/29] Added support for error detection (#33) * Added support for error detection * Update include/EnumCheck.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update test/unit/src/CanMessage_ErrorFrameTests.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Removed file which shouldn't have been committed. * Added missing end of message handling * Fixed brainfart in unit test * Bumped up vesion to 1.7.0 * Fixed formatting * Updated documentation in README with examples. * Added overrides for `<<` operator for output streams. * Made Copilot happy * Changed wrong constant * Fixed stupid bug --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CMakeLists.txt | 2 +- README.md | 188 ++++++++++--------- include/CanDriver.hpp | 34 ++-- include/CanId.hpp | 32 +++- include/CanMessage.hpp | 32 ++++ include/EnumCheck.hpp | 41 ++++ include/can_errors/CanControllerError.hpp | 87 +++++++++ include/can_errors/CanProtocolError.hpp | 160 ++++++++++++++++ include/can_errors/CanTransceiverError.hpp | 95 ++++++++++ src/CanDriver.cpp | 16 +- test/unit/src/CanMessage_ErrorFrameTests.cpp | 131 +++++++++++++ 11 files changed, 695 insertions(+), 123 deletions(-) create mode 100644 include/EnumCheck.hpp create mode 100644 include/can_errors/CanControllerError.hpp create mode 100644 include/can_errors/CanProtocolError.hpp create mode 100644 include/can_errors/CanTransceiverError.hpp create mode 100644 test/unit/src/CanMessage_ErrorFrameTests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 138e558..7e4dbc2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.6.0 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.7.0 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/README.md b/README.md index 4f3cc0e..790d77f 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,8 @@ libsockcanpp was designed with use in CMake projects, but it can also easily be ## Incorporating into Cmake projects: +### Git Clone + 1) clone the repository: `git clone https://github.com/SimonCahill/libsockcanpp.git` 2) add the following to CMakeLists.txt ```cmake @@ -49,137 +51,143 @@ target_link_libraries( 3) generate and build 4) ??? profit -**Disclaimer: This next section was generated by AI. I hope I didn't miss any major errors**. - - -# 📘 CanDriver Usage Guide (with CanId) - -This guide provides a high-level overview and usage examples for the `CanDriver` class in the `libsockcanpp` C++ library. It includes basic setup, message transmission and reception, filter configuration, and integration of `CanId`. - ---- +### CPM +If your project utilises CPM, you can also run -## 🧰 Basic Setup - -```cpp -#include "CanDriver.hpp" - -using namespace sockcanpp; - -int main() { - // Construct CanDriver instance for vcan0 with default settings - CanDriver driver("vcan0", CAN_RAW); - - // Optionally enable telemetry (e.g., timestamped messages) - driver.setCollectTelemetry(true); - - return 0; -} +```cmake +CPMAddPackage( + NAME sockcanpp + GIT_REPOSITORY https://github.com/SimonCahill/libsockcanpp.git + GIT_TAG master +) ``` ---- +# Using libsockcanpp -## 📥 Receiving Messages +## First Steps -```cpp -// Wait for messages (blocking, 3ms timeout) -if (driver.waitForMessages(std::chrono::milliseconds(3))) { - CanMessage msg = driver.readMessage(); +In its current state, libsockcanpp doesn't support CANFD; very basic support exists for CANXL, although it is highly experimental and unstable. - std::cout << "Received ID: " << msg.getCanId().toString() - << " Data: " << msg.getFrameData() << std::endl; -} -``` +This library provides a basic and straightforward API. +The main class is `CanDriver`; it is multi-instance and thread-safe. ---- +### Setting up a CAN Socket -## 📤 Sending Messages +> [!NOTE] +> This library does **NOT** setup the CAN interface. +> This can be done via the `ip` command: +> `$ sudo ip link set dev can0 up type can bitrate ` +> `$ sudo ip link set dev van0 up type vcan` ```cpp -CanId id(0x123); -CanMessage msg(id, "Hello"); +#include -// Send using extended frame (optional) -driver.sendMessage(msg, /* forceExtended = */ true); -``` +using sockcanpp::CanDriver; +using sockcanpp::CanMessage; +using sockcanpp::exceptions::CanException; ---- +int main() { -## 📦 Queueing Messages + CanDriver cDriver{"can0", CAN_RAW}; // No default sender ID + + try { + cDriver.initialiseSocketCan(); + } catch (CanException& ex) { + // handle failure + } -```cpp -std::queue queue; -queue.push(CanMessage(CanId(0x100), "A")); -queue.push(CanMessage(CanId(0x101), "B")); + sendCanFrame(cDriver); + sendMultipleFrames(cDriver); + receiveFrame(cDriver); -driver.sendMessageQueue(queue, std::chrono::milliseconds(10)); -``` + cDriver.setErrorFilter(); // Receive error frames ---- + filtermap_t canFilters{ + { 0x489, 0x7ff } // filter messages with 0x489 as ID + }; + cDriver.setCanFilters(canFilters); // Set X amount of CAN filters. See https://docs.kernel.org/networking/can.html#raw-protocol-sockets-with-can-filters-sock-raw -## 🎯 Using CanId + cDriver.setCollectTelemetry(); // Enable telemetry collection, such as timestamps, data sent, etc. -```cpp -CanId id1(0x1A0); -CanId id2("0x1A1"); // Requires C++ concepts + cDriver.setReceiveOwnMessages(); // Echo sent frames back -// Comparison -if (id1 < id2) { - std::cout << "id1 is less than id2" << std::endl; -} + cDriver.setReturnRelativeTimestamps(); // Enable relative timestamps -// Masking (for filtering) -uint32_t mask = 0x7F0; // Match only the upper bits -driver.setCanFilterMask(mask, id1); +} ``` ---- - -## 🎚️ Configuring Filters +### Sending a single CAN frame ```cpp -sockcanpp::filtermap_t filters = { - { CanId(0x100), 0x7FF }, - { CanId(0x200), 0x700 } -}; - -driver.setCanFilters(filters); +void sendCanFrame(CanDriver& driver) { + CanMessage msg{0x123, "\x01\x02\x03\x04\x05\x06\x07\x08"}; + driver.sendMessage(msg); + driver.sendMessage(msg, true); // force extended CAN frame +} ``` ---- +### Sending multiple CAN frames -## 🚀 Advanced Features +```cpp +void sendMultipleFrames(CanDriver& driver) { + vector messages{ + CanMessage{0x123, "\x01\x02\x03\x04\x05\x06\x07\x08"}, + CanMessage{0x456, "\x09\x10\x11\x12\x13\x14\x15\x16"} + }; -### Enable CAN FD Frames + using namespace std::chrono::literals; -```cpp -driver.allowCanFdFrames(true); + driver.sendMessageQueue(messages); // 20ms delay (Default) + driver.sendMessageQueue(messages, 20ns); // 20ns delay + driver.sendMessageQueue(messages, 20us, true); // 20microseconds delay | force extended +} ``` -### Enable CAN XL Frames (if supported) +### Receiving a frame ```cpp -driver.allowCanXlFrames(true); -``` +void receiveFrame(CanDriver& driver) { + if (!driver.waitForMessages()) { return; } // No messages in buffer -### Receive Own Transmissions + const auto receivedMsg = driver.readMessage(); + std::cout << receivedMsg << std::endl; // Outputs: CanMessage(canId: XXX, data: FF FF FF FF, timestampOffset: Nms) -```cpp -driver.setReceiveOwnMessages(true); + if (receivedMsg.isErrorFrame()) { + handleErrorFrame(receivedMsg); + } +} ``` ---- +### Handling an Error Frame -## 🧪 Reading All Queued Messages +libsockcanpp also provides features for evaluating errors states on the bus. +To enable this feature, `cDriver.setErrorFilter();` needs to be enabled. + +When an error frame is received, the information is provided by the `CanMessage` class. ```cpp -auto allMessages = driver.readQueuedMessages(); -while (!allMessages.empty()) { - const auto& msg = allMessages.front(); - std::cout << msg.getCanId().toString() << ": " << msg.getFrameData() << std::endl; - allMessages.pop(); +void handleErrorFrame(const CanMessage& msg) { + + // Handle as you see fit + + const auto hasBusError = msg.hasBusError(); + const auto hasBusOffError = msg.hasBusOffError(); + const auto hasControllerProblem = msg.hasControllerProblem(); + const auto hasControllerRestarted = msg.hasControllerRestarted(); + const auto hasErrorCounter = msg.hasErrorCounter(); + const auto hasLostArbitration = msg.hasLostArbitration(); + const auto hasProtocolViolation = msg.hasProtocolViolation(); + const auto hasTransceiverStatus = msg.hasTransceiverStatus(); + const auto missingAckOnTransmit = msg.missingAckOnTransmit(); + const auto isTxTimeout = msg.isTxTimeout(); + + const auto controllerError = msg.getControllerError(); + const auto protocolError = msg.getProtocolError(); + const auto transceiverError = msg.getTransceiverError(); + const auto txErrorCounter = msg.getTxErrorCounter(); + const auto rxErrorCounter = msg.getRxErrorCounter(); + const auto arbitrationLostInBit = msg.arbitrationLostInBit(); } ``` ---- - © 2020–2025 Simon Cahill — Licensed under Apache License 2.0 diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index 89e6ee0..cabd546 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -26,6 +26,7 @@ ////////////////////////////// // SYSTEM INCLUDES // ////////////////////////////// +#include #include #include #include @@ -63,6 +64,7 @@ namespace sockcanpp { using tl::optional; #endif // __cplusplus < 201703L + using std::atomic; using std::chrono::microseconds; using std::chrono::milliseconds; using std::chrono::nanoseconds; @@ -104,6 +106,8 @@ namespace sockcanpp { int32_t getMessageQueueSize() const { return this->m_queueSize; } //!< Gets the amount of CAN messages found after last calling waitForMessages() int32_t getSocketFd() const { return this->m_socketFd; } //!< The socket file descriptor used by this instance. + size_t getReceivedMessageCount() const { return this->m_receivedMessages.load(); } //!< The number of messages received by this instance. + string getCanInterface() const { return this->m_canInterface; } //!< The CAN interface used by this instance. public: // +++ I/O +++ @@ -132,9 +136,9 @@ namespace sockcanpp { virtual CanMessage readMessage(); //!< Attempts to read a single message from the bus virtual ssize_t sendMessage(const CanMessage& message, bool forceExtended = false); //!< Attempts to send a single CAN message - virtual ssize_t sendMessageQueue(queue& messages, microseconds delay = sockcanpp_20US, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, microseconds delay, bool forceExtended = false); //!< Attempts to send a queue of messages virtual ssize_t sendMessageQueue(queue& messages, milliseconds delay = sockcanpp_20MS, bool forceExtended = false); //!< Attempts to send a queue of messages - virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay = sockcanpp_20NS, bool forceExtended = false); //!< Attempts to send a queue of messages + virtual ssize_t sendMessageQueue(queue& messages, nanoseconds delay, bool forceExtended = false); //!< Attempts to send a queue of messages virtual queue readQueuedMessages(); //!< Attempts to read all queued messages from the bus @@ -160,24 +164,26 @@ namespace sockcanpp { virtual milliseconds readFrameTimestamp(); private: // +++ Variables +++ - bool m_canReadQueueSize{true}; //!< Is the queue size available - bool m_collectTelemetry{false}; //!< Whether or not to collect telemetry data from the CAN bus - bool m_relativeTimestamps{false}; //!< Whether or not to use relative timestamps + atomic m_receivedMessages{0}; //!< The number of received messages + + bool m_canReadQueueSize{true}; //!< Is the queue size available + bool m_collectTelemetry{false}; //!< Whether or not to collect telemetry data from the CAN bus + bool m_relativeTimestamps{false}; //!< Whether or not to use relative timestamps - CanId m_defaultSenderId; //!< The ID to send messages with if no other ID was set. + CanId m_defaultSenderId; //!< The ID to send messages with if no other ID was set. - filtermap_t m_canFilterMask; //!< The bit mask used to filter CAN messages + filtermap_t m_canFilterMask; //!< The bit mask used to filter CAN messages - int32_t m_canProtocol{CAN_RAW}; //!< The protocol used when communicating via CAN - int32_t m_socketFd{-1}; //!< The CAN socket file descriptor - int32_t m_queueSize{0}; //!< The size of the message queue read by waitForMessages() + int32_t m_canProtocol{CAN_RAW}; //!< The protocol used when communicating via CAN + int32_t m_socketFd{-1}; //!< The CAN socket file descriptor + int32_t m_queueSize{0}; //!< The size of the message queue read by waitForMessages() - optional m_firstTimestamp{}; + optional m_firstTimestamp{}; - mutex m_lock{}; //!< Mutex for thread-safety. - mutex m_lockSend{}; + mutex m_lock{}; //!< Mutex for thread-safety. + mutex m_lockSend{}; - string m_canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) + string m_canInterface; //!< The CAN interface used for communication (e.g. can0, can1, ...) }; diff --git a/include/CanId.hpp b/include/CanId.hpp index efae908..b83f22d 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -26,13 +26,18 @@ ////////////////////////////// // SYSTEM INCLUDES // ////////////////////////////// +// stl #include #include #include #include -#include +#include #include +// libc +#include +#include + #if __cpp_concepts >= 201907 template concept Stringable = requires(Str s) { { s.data() + s.size() } -> std::convertible_to; }; @@ -74,6 +79,11 @@ namespace sockcanpp { public: // +++ Operators +++ constexpr canid_t operator *() const { return m_identifier; } //!< Returns the raw CAN ID value. + friend std::ostream& operator <<(std::ostream& os, const CanId& id) { + os << std::hex << id.m_identifier; + return os; + } //!< Outputs the CanId to a stream in hexadecimal format. + #pragma region "Conversions" constexpr operator int16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } constexpr operator uint16_t() const { return static_cast(m_identifier) & CAN_ERR_MASK; } @@ -269,14 +279,26 @@ namespace sockcanpp { } public: // +++ Getters +++ - constexpr bool hasErrorFrameFlag() const { return isErrorFrame(m_identifier); } //!< Indicates whether or not this ID is an error frame. - constexpr bool hasRtrFrameFlag() const { return isRemoteTransmissionRequest(m_identifier); } //!< Indicates whether or not this ID is a remote transmission request. - constexpr bool isStandardFrameId() const { return !isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is a standard frame ID. - constexpr bool isExtendedFrameId() const { return isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is an extended frame ID. + constexpr bool hasErrorFrameFlag() const { return isErrorFrame(m_identifier); } //!< Indicates whether or not this ID is an error frame. + constexpr bool hasRtrFrameFlag() const { return isRemoteTransmissionRequest(m_identifier); } //!< Indicates whether or not this ID is a remote transmission request. + constexpr bool isStandardFrameId() const { return !isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is a standard frame ID. + constexpr bool isExtendedFrameId() const { return isExtendedFrame(m_identifier); } //!< Indicates whether or not this ID is an extended frame ID. public: // +++ Equality Checks +++ constexpr bool equals(const CanId& otherId) const { return m_identifier == otherId.m_identifier; } //!< Compares this ID to another. + public: // +++ Error Frame Handling +++ + constexpr bool hasBusError() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_BUSERROR); } //!< Checks if this ID has a bus error. + constexpr bool hasBusOffError() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_BUSOFF); } //!< Checks if this ID has a bus-off error. + constexpr bool hasControllerProblem() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_CRTL); } //!< Checks if this ID has a controller problem. + constexpr bool hasControllerRestarted() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_RESTARTED); } //!< Checks if this ID has a controller restarted error. + constexpr bool hasErrorCounter() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_CNT); } //!< Checks if this ID has an error counter. + constexpr bool hasLostArbitration() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_LOSTARB); } //!< Checks if this ID has lost arbitration. + constexpr bool hasProtocolViolation() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_PROT); } //!< Checks if this ID has a protocol violation. + constexpr bool hasTransceiverStatus() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_TRX); } //!< Checks if this ID has a transceiver status error. + constexpr bool missingAckOnTransmit() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_ACK); } //!< Checks if this ID is missing an ACK on transmit. + constexpr bool isTxTimeout() const { return hasErrorFrameFlag() && (m_identifier & CAN_ERR_TX_TIMEOUT); } //!< Checks if this ID is a transmission timeout error frame. + private: // +++ Variables +++ uint32_t m_identifier = 0; }; diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index 11fbc5a..2c6ae16 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -42,6 +42,9 @@ // LOCAL INCLUDES // ////////////////////////////// #include "CanId.hpp" +#include "can_errors/CanControllerError.hpp" +#include "can_errors/CanProtocolError.hpp" +#include "can_errors/CanTransceiverError.hpp" namespace sockcanpp { @@ -134,6 +137,15 @@ namespace sockcanpp { return data; } //!< Returns the frame data as a string. + friend std::ostream& operator<<(std::ostream& os, const CanMessageT& msg) { + os << "CanMessage(canId: " << msg.getCanId() << ", data: "; + for (size_t i = 0; i < msg.m_rawFrame.can_dlc; ++i) { + os << std::hex << static_cast(msg.m_rawFrame.data[i]) << " "; + } + os << ", timestampOffset: " << msg.m_timestampOffset.count() << "ms)"; + return os; + } //!< Outputs the CanMessage to a stream in a human-readable format. + const can_frame& getRawFrame() const noexcept { return m_rawFrame; } //!< Returns the raw can_frame structure of this message. const Duration& getTimestampOffset() const noexcept { return m_timestampOffset; } //!< Returns the timestamp offset of this message. @@ -146,6 +158,26 @@ namespace sockcanpp { [[nodiscard]] constexpr bool isExtendedFrameId() const noexcept { return m_canIdentifier.isExtendedFrameId(); } //!< Checks if the CAN message has an extended frame ID. + public: // +++ Error Frame Handling +++ + constexpr bool hasBusError() const { return m_canIdentifier.hasBusError(); } + constexpr bool hasBusOffError() const { return m_canIdentifier.hasBusOffError(); } + constexpr bool hasControllerProblem() const { return m_canIdentifier.hasControllerProblem(); } + constexpr bool hasControllerRestarted() const { return m_canIdentifier.hasControllerRestarted(); } + constexpr bool hasErrorCounter() const { return m_canIdentifier.hasErrorCounter(); } + constexpr bool hasLostArbitration() const { return m_canIdentifier.hasLostArbitration(); } + constexpr bool hasProtocolViolation() const { return m_canIdentifier.hasProtocolViolation(); } + constexpr bool hasTransceiverStatus() const { return m_canIdentifier.hasTransceiverStatus(); } + constexpr bool missingAckOnTransmit() const { return m_canIdentifier.missingAckOnTransmit(); } + constexpr bool isTxTimeout() const { return m_canIdentifier.isTxTimeout(); } + + public: // +++ Errors +++ + can_errors::ControllerError getControllerError() const { return can_errors::ControllerError::fromErrorCode(static_cast(m_rawFrame.data[1])); } + can_errors::ProtocolError getProtocolError() const { return can_errors::ProtocolError::fromErrorCode(static_cast(m_rawFrame.data[2]), static_cast(m_rawFrame.data[3])); } + can_errors::TransceiverError getTransceiverError() const { return can_errors::TransceiverError::fromErrorCode(static_cast(m_rawFrame.data[4])); } + size_t getTxErrorCounter() const { return m_rawFrame.data[6]; } + size_t getRxErrorCounter() const { return m_rawFrame.data[7]; } + uint8_t arbitrationLostInBit() const { return m_rawFrame.data[0]; } + public: // +++ Equality Checks +++ bool operator==(const CanMessageT& other) const noexcept { return m_canIdentifier == other.m_canIdentifier && diff --git a/include/EnumCheck.hpp b/include/EnumCheck.hpp new file mode 100644 index 0000000..a83db02 --- /dev/null +++ b/include/EnumCheck.hpp @@ -0,0 +1,41 @@ +/** + * @file EnumCheck.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the implementation of a constexpr enumeration value checker. + * @version 0.1 + * @date 2025-03-25 + * + * The entirety of this code is copied from https://stackoverflow.com/a/33091821/2921426 + * + * @copyright Copyright (c) 2025 Simon Cahill. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_ENUMCHECK_HPP +#define LIBSOCKCANPP_INCLUDE_ENUMCHECK_HPP + +namespace sockcanpp { + + template + class EnumCheck; + + template + class EnumCheck { + public: + template + static bool constexpr is_value(IntType) { return false; } + }; + + template + class EnumCheck : private EnumCheck { + using super = EnumCheck; + + public: + template + static bool constexpr is_value(IntType v) { + return v == static_cast(V) || super::is_value(v); + } + }; + +} + +#endif // LIBSOCKCANPP_INCLUDE_ENUMCHECK_HPP \ No newline at end of file diff --git a/include/can_errors/CanControllerError.hpp b/include/can_errors/CanControllerError.hpp new file mode 100644 index 0000000..eebf582 --- /dev/null +++ b/include/can_errors/CanControllerError.hpp @@ -0,0 +1,87 @@ +/** + * @file CanControllerError.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the definition of CAN controller error codes. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANCONTROLLERERROR_HPP +#define LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANCONTROLLERERROR_HPP + +#include +#include + +#include + +#if __cplusplus >= 201703L +namespace sockcanpp::can_errors { +#else +namespace sockcanpp { namespace can_errors { +#endif // __cplusplus >= 201703L + + using std::string; + + /** + * @brief Contains a typesafe representation of CAN controller error codes. + */ + enum class ControllerErrorCode: uint8_t { + UNSPECIFIED_ERROR = CAN_ERR_CRTL_UNSPEC, //!< Unspecified error. + RECEIVE_OVERFLOW = CAN_ERR_CRTL_RX_OVERFLOW, //!< Receive overflow error. + TRANSMIT_OVERFLOW = CAN_ERR_CRTL_TX_OVERFLOW, //!< Transmit overflow error. + RECEIVE_WARNING = CAN_ERR_CRTL_RX_WARNING, //!< Receive warning error. + TRANSMIT_WARNING = CAN_ERR_CRTL_TX_WARNING, //!< Transmit warning error. + RECEIVE_PASSIVE = CAN_ERR_CRTL_RX_PASSIVE, //!< Receive passive error. + TRANSMIT_PASSIVE = CAN_ERR_CRTL_TX_PASSIVE, //!< Transmit passive error. + RECOVERED_ACTIVE = CAN_ERR_CRTL_ACTIVE, //!< Recovered to active state. + }; + + struct ControllerError { + ControllerErrorCode errorCode; + string errorMessage; + + ControllerError(ControllerErrorCode code, const string& message): errorCode(code), errorMessage(message) { } + + static ControllerError fromErrorCode(ControllerErrorCode code) { + switch (code) { + case ControllerErrorCode::UNSPECIFIED_ERROR: return ControllerError(code, "Unspecified error"); + case ControllerErrorCode::RECEIVE_OVERFLOW: return ControllerError(code, "Receive overflow error"); + case ControllerErrorCode::TRANSMIT_OVERFLOW: return ControllerError(code, "Transmit overflow error"); + case ControllerErrorCode::RECEIVE_WARNING: return ControllerError(code, "Receive warning error"); + case ControllerErrorCode::TRANSMIT_WARNING: return ControllerError(code, "Transmit warning error"); + case ControllerErrorCode::RECEIVE_PASSIVE: return ControllerError(code, "Receive passive error"); + case ControllerErrorCode::TRANSMIT_PASSIVE: return ControllerError(code, "Transmit passive error"); + case ControllerErrorCode::RECOVERED_ACTIVE: return ControllerError(code, "Recovered to active state"); + default: return ControllerError(code, "Unknown error"); + } + } + }; + +#if __cplusplus >= 201703L +} // namespace sockcanpp::can_errors +#else +} /* namespace can_errors */ } /* namespace sockcanpp */ +#endif // __cplusplus >= 201703L + +namespace std { + + inline string to_string(sockcanpp::can_errors::ControllerErrorCode err) { + using sockcanpp::can_errors::ControllerErrorCode; + switch (err) { + case ControllerErrorCode::UNSPECIFIED_ERROR: return "Unspecified error"; + case ControllerErrorCode::RECEIVE_OVERFLOW: return "Receive overflow error"; + case ControllerErrorCode::TRANSMIT_OVERFLOW: return "Transmit overflow error"; + case ControllerErrorCode::RECEIVE_WARNING: return "Receive warning error"; + case ControllerErrorCode::TRANSMIT_WARNING: return "Transmit warning error"; + case ControllerErrorCode::RECEIVE_PASSIVE: return "Receive passive error"; + case ControllerErrorCode::TRANSMIT_PASSIVE: return "Transmit passive error"; + case ControllerErrorCode::RECOVERED_ACTIVE: return "Recovered to active state"; + default: return "Unknown controller error"; + } + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANCONTROLLERERROR_HPP \ No newline at end of file diff --git a/include/can_errors/CanProtocolError.hpp b/include/can_errors/CanProtocolError.hpp new file mode 100644 index 0000000..e3cd2c3 --- /dev/null +++ b/include/can_errors/CanProtocolError.hpp @@ -0,0 +1,160 @@ +/** + * @file CanProtocolError.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the definition of CAN protocol error codes. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANPROTOCOLERROR_HPP +#define LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANPROTOCOLERROR_HPP + +#include +#include + +#include + +#if __cplusplus >= 201703L +namespace sockcanpp::can_errors { +#else +namespace sockcanpp { namespace can_errors { +#endif // __cplusplus >= 201703L + + using std::string; + + /** + * @brief Contains a typesafe representation of CAN protocol error codes. + */ + enum class ProtocolErrorCode: uint8_t { + UNSPECIFIED_ERROR = CAN_ERR_PROT_UNSPEC, //!< Unspecified error occurred. + SINGLE_BIT_ERROR = CAN_ERR_PROT_BIT, //!< A single bit error occurred. + FRAME_FORMAT_ERROR = CAN_ERR_PROT_FORM, //!< A frame format error occurred. + BIT_STUFFING_ERROR = CAN_ERR_PROT_STUFF, //!< A bit stuffing error occurred. + DOMINANT_BIT_FAIL = CAN_ERR_PROT_BIT0, //!< A dominant bit failure occurred. + RECESSIVE_BIT_FAIL = CAN_ERR_PROT_BIT1, //!< A recessive bit failure occurred. + OVERLOAD_ERROR = CAN_ERR_PROT_OVERLOAD,//!< An overload error occurred. + ACTIVE_ERROR = CAN_ERR_PROT_ACTIVE, //!< An active error occurred. + TX_ERROR = CAN_ERR_PROT_TX //!< A transmission error occurred. + }; + + /** + * @brief Contains a typesafe representation of CAN protocol error locations. + */ + enum class ProtocolErrorLocation: uint8_t { + UNSPECIFIED_LOCATION = CAN_ERR_PROT_LOC_UNSPEC, //!< Unspecified location. + START_OF_FRAME = CAN_ERR_PROT_LOC_SOF, //!< Start of frame. + IDBIT_28_21 = CAN_ERR_PROT_LOC_ID28_21, //!< ID bits 28 - 21 (SFF: 10 - 3) + IDBIT_20_18 = CAN_ERR_PROT_LOC_ID20_18, //!< ID bits 20 - 18 (SFF: 2 - 0) + SUBSTITUTE_RTR = CAN_ERR_PROT_LOC_SRTR, //!< Substitute RTR bit. + IDENTIFIER_EXTENSION = CAN_ERR_PROT_LOC_IDE, //!< Identifier extension bit. + IDBIT_17_13 = CAN_ERR_PROT_LOC_ID17_13, //!< ID bits 17 - 13 + IDBIT_12_05 = CAN_ERR_PROT_LOC_ID12_05, //!< ID bits 12 - 05 + IDBIT_04_00 = CAN_ERR_PROT_LOC_ID04_00, //!< ID bits 04 - 00 + REMOTE_TRANSMIT_REQ = CAN_ERR_PROT_LOC_RTR, //!< Remote transmit request bit. + RESERVED_BIT_1 = CAN_ERR_PROT_LOC_RES1, //!< Reserved bit 1. + RESERVED_BIT_0 = CAN_ERR_PROT_LOC_RES0, //!< Reserved bit 0. + DATA_LENGTH_CODE = CAN_ERR_PROT_LOC_DLC, //!< Data length code. + DATA_SECTION = CAN_ERR_PROT_LOC_DATA, //!< Data section. + CRC_SECTION = CAN_ERR_PROT_LOC_CRC_SEQ, //!< CRC section. + CRC_DELIMITER = CAN_ERR_PROT_LOC_CRC_DEL, //!< CRC delimiter. + ACK_SLOT = CAN_ERR_PROT_LOC_ACK, //!< ACK slot. + ACK_DELIMITER = CAN_ERR_PROT_LOC_ACK_DEL, //!< ACK delimiter. + END_OF_FRAME = CAN_ERR_PROT_LOC_EOF, //!< End of frame. + INTERMISSION = CAN_ERR_PROT_LOC_INTERM, //!< Intermission section. + }; + + struct ProtocolError { + ProtocolErrorCode errorCode; + ProtocolErrorLocation errorLocation; + string errorMessage; + + ProtocolError(ProtocolErrorCode code, ProtocolErrorLocation location, const string& message): errorCode(code), errorLocation(location), errorMessage(message) { } + + static ProtocolError fromErrorCode(ProtocolErrorCode code, ProtocolErrorLocation location) { + switch (code) { + case ProtocolErrorCode::UNSPECIFIED_ERROR: return ProtocolError(code, location, "Unspecified error occurred"); + case ProtocolErrorCode::SINGLE_BIT_ERROR: return ProtocolError(code, location, "Single bit error occurred"); + case ProtocolErrorCode::FRAME_FORMAT_ERROR: return ProtocolError(code, location, "Frame format error occurred"); + case ProtocolErrorCode::BIT_STUFFING_ERROR: return ProtocolError(code, location, "Bit stuffing error occurred"); + case ProtocolErrorCode::DOMINANT_BIT_FAIL: return ProtocolError(code, location, "Dominant bit failure occurred"); + case ProtocolErrorCode::RECESSIVE_BIT_FAIL: return ProtocolError(code, location, "Recessive bit failure occurred"); + case ProtocolErrorCode::OVERLOAD_ERROR: return ProtocolError(code, location, "Overload error occurred"); + case ProtocolErrorCode::ACTIVE_ERROR: return ProtocolError(code, location, "Active error occurred"); + case ProtocolErrorCode::TX_ERROR: return ProtocolError(code, location, "Transmission error occurred"); + default: return ProtocolError(code, location, "Unknown error occurred"); + } + } + }; + +#if __cplusplus >= 201703L +} // namespace sockcanpp::can_errors +#else +} /* namespace can_errors */ } /* namespace sockcanpp */ +#endif // __cplusplus >= 201703L + +namespace std { + + inline string to_string(sockcanpp::can_errors::ProtocolErrorLocation loc) { + using sockcanpp::can_errors::ProtocolErrorLocation; + switch (loc) { + case ProtocolErrorLocation::UNSPECIFIED_LOCATION: return "Unspecified location."; + case ProtocolErrorLocation::START_OF_FRAME: return "Start of frame."; + case ProtocolErrorLocation::IDBIT_28_21: return "ID bits 28 - 21 (SFF: 10 - 3)"; + case ProtocolErrorLocation::IDBIT_20_18: return "ID bits 20 - 18 (SFF: 2 - 0)"; + case ProtocolErrorLocation::SUBSTITUTE_RTR: return "Substitute RTR bit."; + case ProtocolErrorLocation::IDENTIFIER_EXTENSION: return "Identifier extension bit."; + case ProtocolErrorLocation::IDBIT_17_13: return "ID bits 17 - 13"; + case ProtocolErrorLocation::IDBIT_12_05: return "ID bits 12 - 05"; + case ProtocolErrorLocation::IDBIT_04_00: return "ID bits 04 - 00"; + case ProtocolErrorLocation::REMOTE_TRANSMIT_REQ: return "Remote transmit request bit."; + case ProtocolErrorLocation::RESERVED_BIT_1: return "Reserved bit 1."; + case ProtocolErrorLocation::RESERVED_BIT_0: return "Reserved bit 0."; + case ProtocolErrorLocation::DATA_LENGTH_CODE: return "Data length code."; + case ProtocolErrorLocation::DATA_SECTION: return "Data section."; + case ProtocolErrorLocation::CRC_SECTION: return "CRC section."; + case ProtocolErrorLocation::CRC_DELIMITER: return "CRC delimiter."; + case ProtocolErrorLocation::ACK_SLOT: return "ACK slot."; + case ProtocolErrorLocation::ACK_DELIMITER: return "ACK delimiter."; + case ProtocolErrorLocation::END_OF_FRAME: return "End of frame."; + case ProtocolErrorLocation::INTERMISSION: return "Intermission section."; + default: return "Unknown location."; + } + } + + inline string to_string(sockcanpp::can_errors::ProtocolErrorCode err) { + using sockcanpp::can_errors::ProtocolErrorCode; + switch (err) { + case ProtocolErrorCode::UNSPECIFIED_ERROR: return "Unspecified error occurred"; + case ProtocolErrorCode::SINGLE_BIT_ERROR: return "Single bit error occurred"; + case ProtocolErrorCode::FRAME_FORMAT_ERROR: return "Frame format error occurred"; + case ProtocolErrorCode::BIT_STUFFING_ERROR: return "Bit stuffing error occurred"; + case ProtocolErrorCode::DOMINANT_BIT_FAIL: return "Dominant bit failure occurred"; + case ProtocolErrorCode::RECESSIVE_BIT_FAIL: return "Recessive bit failure occurred"; + case ProtocolErrorCode::OVERLOAD_ERROR: return "Overload error occurred"; + case ProtocolErrorCode::ACTIVE_ERROR: return "Active error occurred"; + case ProtocolErrorCode::TX_ERROR: return "Transmission error occurred"; + default: return "Unknown error occurred"; + } + } + + inline string to_string(sockcanpp::can_errors::ProtocolErrorCode err, sockcanpp::can_errors::ProtocolErrorLocation loc) { + using sockcanpp::can_errors::ProtocolErrorCode; + switch (err) { + case ProtocolErrorCode::UNSPECIFIED_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::SINGLE_BIT_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::FRAME_FORMAT_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::BIT_STUFFING_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::DOMINANT_BIT_FAIL: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::RECESSIVE_BIT_FAIL: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::OVERLOAD_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::ACTIVE_ERROR: return to_string(err) + " at " + to_string(loc); + case ProtocolErrorCode::TX_ERROR: return to_string(err) + " at " + to_string(loc); + default: return to_string(err) + " at " + to_string(loc); + } + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANPROTOCOLERROR_HPP \ No newline at end of file diff --git a/include/can_errors/CanTransceiverError.hpp b/include/can_errors/CanTransceiverError.hpp new file mode 100644 index 0000000..ce56151 --- /dev/null +++ b/include/can_errors/CanTransceiverError.hpp @@ -0,0 +1,95 @@ +/** + * @file CanTransceiverError.hpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains the definition of CAN transceiver error codes. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#ifndef LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANTRANSCEIVERERROR_HPP +#define LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANTRANSCEIVERERROR_HPP + +#include +#include + +#include + +#if __cplusplus >= 201703L +namespace sockcanpp::can_errors { +#else +namespace sockcanpp { namespace can_errors { +#endif // __cplusplus >= 201703L + + using std::string; + + /** + * @brief Contains a typesafe representation of CAN transceiver error codes. + */ + enum class TransceiverErrorCode: uint8_t { + // CANH CANL + UNSPECIFIED_ERROR = CAN_ERR_TRX_UNSPEC, //!< Unspecified error. 0000 0000 + CAN_HIGH_NO_WIRE = CAN_ERR_TRX_CANH_NO_WIRE, //!< CANH no wire error. 0000 0100 + CAN_HIGH_SHORT_TO_BAT = CAN_ERR_TRX_CANH_SHORT_TO_BAT, //!< CANH short to battery error. 0000 0101 + CAN_HIGH_SHORT_TO_VCC = CAN_ERR_TRX_CANH_SHORT_TO_VCC, //!< CANH short to VCC error. 0000 0110 + CAN_HIGH_SHORT_TO_GND = CAN_ERR_TRX_CANH_SHORT_TO_GND, //!< CANH short to ground error. 0000 0111 + + CAN_LOW_NO_WIRE = CAN_ERR_TRX_CANL_NO_WIRE, //!< CANL no wire error. 0100 0000 + CAN_LOW_SHORT_TO_BAT = CAN_ERR_TRX_CANL_SHORT_TO_BAT, //!< CANL short to battery error. 0101 0000 + CAN_LOW_SHORT_TO_VCC = CAN_ERR_TRX_CANL_SHORT_TO_VCC, //!< CANL short to VCC error. 0110 0000 + CAN_LOW_SHORT_TO_GND = CAN_ERR_TRX_CANL_SHORT_TO_GND, //!< CANL short to ground error. 0111 0000 + CAN_LOW_SHORT_TO_HIGH = CAN_ERR_TRX_CANL_SHORT_TO_CANH, //!< CANL short to CANH error. 1000 0000 + }; + + struct TransceiverError { + TransceiverErrorCode errorCode; + string errorMessage; + + TransceiverError(TransceiverErrorCode code, const string& message): errorCode(code), errorMessage(message) { } + + static TransceiverError fromErrorCode(TransceiverErrorCode code) { + switch (code) { + case TransceiverErrorCode::UNSPECIFIED_ERROR: return TransceiverError(code, "Unspecified error."); + case TransceiverErrorCode::CAN_HIGH_NO_WIRE: return TransceiverError(code, "CANH no wire error."); + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_BAT: return TransceiverError(code, "CANH short to battery error."); + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_VCC: return TransceiverError(code, "CANH short to VCC error."); + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_GND: return TransceiverError(code, "CANH short to ground error."); + case TransceiverErrorCode::CAN_LOW_NO_WIRE: return TransceiverError(code, "CANL no wire error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_BAT: return TransceiverError(code, "CANL short to battery error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_VCC: return TransceiverError(code, "CANL short to VCC error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_GND: return TransceiverError(code, "CANL short to ground error."); + case TransceiverErrorCode::CAN_LOW_SHORT_TO_HIGH: return TransceiverError(code, "CANL short to CANH error."); + default: return TransceiverError(code, "Unknown error."); + } + } + }; + +#if __cplusplus >= 201703L +} // namespace sockcanpp::can_errors +#else +} /* namespace can_errors */ } /* namespace sockcanpp */ +#endif // __cplusplus >= 201703L + +namespace std { + + inline string to_string(sockcanpp::can_errors::TransceiverErrorCode err) { + using sockcanpp::can_errors::TransceiverErrorCode; + switch (err) { + case TransceiverErrorCode::UNSPECIFIED_ERROR: return "Unspecified error."; + case TransceiverErrorCode::CAN_HIGH_NO_WIRE: return "CANH no wire error."; + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_BAT: return "CANH short to battery error."; + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_VCC: return "CANH short to VCC error."; + case TransceiverErrorCode::CAN_HIGH_SHORT_TO_GND: return "CANH short to ground error."; + case TransceiverErrorCode::CAN_LOW_NO_WIRE: return "CANL no wire error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_BAT: return "CANL short to battery error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_VCC: return "CANL short to VCC error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_GND: return "CANL short to ground error."; + case TransceiverErrorCode::CAN_LOW_SHORT_TO_HIGH: return "CANL short to CANH error."; + default: return "Unknown error."; + } + } + +} + +#endif // LIBSOCKCANPP_INCLUDE_CAN_ERRORS_CANTRANSCEIVERERROR_HPP \ No newline at end of file diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 82d5122..46ff7d5 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -321,20 +321,10 @@ namespace sockcanpp { bool more{true}; do { - ssize_t readBytes; - can_frame canFrame{}; - readBytes = read(m_socketFd, &canFrame, sizeof(can_frame)); - if (readBytes >= 0) { - if (m_collectTelemetry) { - // Read timestamp from the socket if available. - messages.emplace(CanMessage{canFrame, readFrameTimestamp()}); - } else { - messages.emplace(canFrame); - } - } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + try { + messages.emplace(readMessageLock(false)); // Read a message without locking the mutex + } catch (const std::exception& e) { more = false; - } else { - throw CanException(formatString("FAILED to read from CAN! Error: %d => %s", errno, strerror(errno)), m_socketFd); } } while (more); } diff --git a/test/unit/src/CanMessage_ErrorFrameTests.cpp b/test/unit/src/CanMessage_ErrorFrameTests.cpp new file mode 100644 index 0000000..c9321a8 --- /dev/null +++ b/test/unit/src/CanMessage_ErrorFrameTests.cpp @@ -0,0 +1,131 @@ +/** + * @file CanMessage_ErrorFrameTests.cpp + * @author Simon Cahill (contact@simonc.eu) + * @brief Contains all the unit tests for the error frame handling in the CanMessage structure. + * @version 0.1 + * @date 2025-08-05 + * + * @copyright Copyright (c) 2025 Simon Cahill and Contributors. + */ + +#include + +#include + +#include + +using sockcanpp::CanId; +using sockcanpp::CanMessage; +using namespace sockcanpp::can_errors; + +using std::array; +using std::string; + +constexpr CanId g_testCanId(0x123); + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectTxTimeout) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_TX_TIMEOUT), ""); + + ASSERT_TRUE(msg.isTxTimeout()); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_Bit1) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), "\x01"); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), 1); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_Bit10) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), "\x0a"); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), 10); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_Bit100) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), "\x64"); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), 100); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectLostArbitration_TestAllBits) { + for (uint8_t i = 0; i < 0xff; i++) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_LOSTARB), string(1, i)); + + ASSERT_TRUE(msg.hasLostArbitration()); + ASSERT_EQ(msg.arbitrationLostInBit(), i); + } +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectUnspecified) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x00"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::UNSPECIFIED_ERROR); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRxOverflow) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x01"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECEIVE_OVERFLOW); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectTxOverflow) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x02"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::TRANSMIT_OVERFLOW); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRxWarning) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x04"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECEIVE_WARNING); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectTxWarning) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x08"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::TRANSMIT_WARNING); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRxPassive) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x10"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECEIVE_PASSIVE); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectTxPassive) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x20"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::TRANSMIT_PASSIVE); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ControllerProblem_ExpectRecoveredActive) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_CRTL), "\xff\x40"); + + ASSERT_TRUE(msg.hasControllerProblem()); + ASSERT_EQ(msg.getControllerError().errorCode, ControllerErrorCode::RECOVERED_ACTIVE); +} + +TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ProtocolError_ExpectCombinations) { + array protType{ 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; + array protLocation{ 0x00, 0x03, 0x02, 0x06, 0x04, 0x05, 0x07, 0x0F, 0x0E, 0x0C, 0x0D, 0x09, 0x0B, 0x0A, 0x08, 0x18, 0x19, 0x1B, 0x1A, 0x12 }; + + for (const auto i : protType) { + for (const auto j : protLocation) { + CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_PROT), string({ static_cast(0xff), static_cast(0xff), static_cast(i), static_cast(j) })); + + ASSERT_TRUE(msg.hasProtocolViolation()); + ASSERT_EQ(msg.getProtocolError().errorCode, static_cast(i)); + ASSERT_EQ(msg.getProtocolError().errorLocation, static_cast(j)); + } + } +} From 743bb44cb14010f8ca8de45d54a5938f5ba0b6d8 Mon Sep 17 00:00:00 2001 From: SimonC Date: Tue, 14 Oct 2025 23:45:05 +0200 Subject: [PATCH 24/29] Fixed build on some systems, most notably fixed LLVM builds. (#34) * Some toolchains/distros apparently don't include CAN_ERR_CNT as a #define * Fixed build on LLVM. * Added cstdint header. Not including this seems to break some applications using the library. * Update include/CanId.hpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Updated version --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CMakeLists.txt | 2 +- include/CanId.hpp | 12 ++++++++++++ include/CanMessage.hpp | 14 ++++++++++---- test/unit/src/CanMessage_ErrorFrameTests.cpp | 2 -- toolchains/x86_64_llvm_toolchain.cmake | 16 ++++++++++++++++ 5 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 toolchains/x86_64_llvm_toolchain.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e4dbc2..b65174f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.7.0 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.7.1 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/include/CanId.hpp b/include/CanId.hpp index b83f22d..f5977e1 100644 --- a/include/CanId.hpp +++ b/include/CanId.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +53,17 @@ template concept ConvertibleToCanId = Stringable || Integral || CChar; #endif +#ifndef CAN_ERR_CNT +/** + * Fallback definition for CAN_ERR_CNT. + * The value 0x00000200U is defined by the Linux CAN subsystem (see ). + * This fallback is provided in case the system headers do not define CAN_ERR_CNT, + * such as when building on non-Linux systems or with older kernel headers. + * See: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/can/error.h + */ +#define CAN_ERR_CNT 0x00000200U +#endif // CAN_ERR_CNT + namespace sockcanpp { using std::bitset; diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index 2c6ae16..abde778 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -64,6 +64,12 @@ namespace sockcanpp { using std::string_view; //!< Use string_view if available, otherwise use string. #endif // __cpp_lib_string_view + #if __cplusplus >= 201703L + #define NO_DISCARD [[nodiscard]] + #else + #define NO_DISCARD + #endif // __cplusplus >= 201703L + /** * @brief Represents a CAN message that was received. */ @@ -150,13 +156,13 @@ namespace sockcanpp { const Duration& getTimestampOffset() const noexcept { return m_timestampOffset; } //!< Returns the timestamp offset of this message. - [[nodiscard]] constexpr bool isErrorFrame() const noexcept { return m_canIdentifier.hasErrorFrameFlag(); } //!< Checks if the CAN message is an error frame. + NO_DISCARD constexpr bool isErrorFrame() const noexcept { return m_canIdentifier.hasErrorFrameFlag(); } //!< Checks if the CAN message is an error frame. - [[nodiscard]] constexpr bool isRemoteTransmissionRequest() const noexcept { return m_canIdentifier.hasRtrFrameFlag(); } //!< Checks if the CAN message is a remote transmission request. + NO_DISCARD constexpr bool isRemoteTransmissionRequest() const noexcept { return m_canIdentifier.hasRtrFrameFlag(); } //!< Checks if the CAN message is a remote transmission request. - [[nodiscard]] constexpr bool isStandardFrameId() const noexcept { return m_canIdentifier.isStandardFrameId(); } //!< Checks if the CAN message has a standard frame ID. + NO_DISCARD constexpr bool isStandardFrameId() const noexcept { return m_canIdentifier.isStandardFrameId(); } //!< Checks if the CAN message has a standard frame ID. - [[nodiscard]] constexpr bool isExtendedFrameId() const noexcept { return m_canIdentifier.isExtendedFrameId(); } //!< Checks if the CAN message has an extended frame ID. + NO_DISCARD constexpr bool isExtendedFrameId() const noexcept { return m_canIdentifier.isExtendedFrameId(); } //!< Checks if the CAN message has an extended frame ID. public: // +++ Error Frame Handling +++ constexpr bool hasBusError() const { return m_canIdentifier.hasBusError(); } diff --git a/test/unit/src/CanMessage_ErrorFrameTests.cpp b/test/unit/src/CanMessage_ErrorFrameTests.cpp index c9321a8..d707b5c 100644 --- a/test/unit/src/CanMessage_ErrorFrameTests.cpp +++ b/test/unit/src/CanMessage_ErrorFrameTests.cpp @@ -21,8 +21,6 @@ using namespace sockcanpp::can_errors; using std::array; using std::string; -constexpr CanId g_testCanId(0x123); - TEST(CanMessageErrorFrameTests, CanMessage_ErrorFrame_ExpectTxTimeout) { CanMessage msg(CanId(CAN_ERR_FLAG | CAN_ERR_TX_TIMEOUT), ""); diff --git a/toolchains/x86_64_llvm_toolchain.cmake b/toolchains/x86_64_llvm_toolchain.cmake new file mode 100644 index 0000000..c72bb41 --- /dev/null +++ b/toolchains/x86_64_llvm_toolchain.cmake @@ -0,0 +1,16 @@ +######################################################################### +# ___ __ __ _ _ _ _ __ ____ __ # +# __ _( _ ) / / / /| | | | | | |\ \ / / \/ | # +# \ \ / _ \/ _ \ / _ \_ _| | |__| |_\ V /| |\/| | # +# /_\_\___/\___/_\___/ |_| |____|____\_/ |_| |_| # +# |___| # +######################################################################### + +set(CMAKE_SYSTEM_NAME Linux) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +### +# Configure compiler suite +### +set(CMAKE_C_COMPILER clang) +set(CMAKE_CXX_COMPILER clang++) \ No newline at end of file From 4de7fd678aa87ccf65b14657a300563dde9da23b Mon Sep 17 00:00:00 2001 From: SimonC Date: Tue, 4 Nov 2025 22:57:32 +0100 Subject: [PATCH 25/29] Fixes #35 (#36) * Fixes #35 * Bumped up version. --- CMakeLists.txt | 2 +- include/CanDriver.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b65174f..81ee852 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.7.1 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.7.2 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index cabd546..519ae15 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -155,9 +155,9 @@ namespace sockcanpp { virtual void setReceiveOwnMessages(const bool enabled = true) const; //!< Sets the receive own messages option for the interface virtual void setReturnRelativeTimestamps(const bool enabled = true) { m_relativeTimestamps = enabled; } - protected: // +++ Socket Management +++ - virtual void initialiseSocketCan(); //!< Initialises socketcan - virtual void uninitialiseSocketCan(); //!< Uninitialises socketcan + private: // +++ Socket Management +++ + void initialiseSocketCan(); //!< Initialises socketcan + void uninitialiseSocketCan(); //!< Uninitialises socketcan private: // +++ Member Functions +++ virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard From ea82b973f47f6b2ecf703b7cf02854e55c866b79 Mon Sep 17 00:00:00 2001 From: SimonC Date: Tue, 4 Nov 2025 23:03:37 +0100 Subject: [PATCH 26/29] Updated README to reflect changes made in PR #36 (#37) --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 790d77f..c1b43cc 100644 --- a/README.md +++ b/README.md @@ -89,12 +89,6 @@ using sockcanpp::exceptions::CanException; int main() { CanDriver cDriver{"can0", CAN_RAW}; // No default sender ID - - try { - cDriver.initialiseSocketCan(); - } catch (CanException& ex) { - // handle failure - } sendCanFrame(cDriver); sendMultipleFrames(cDriver); From d4594382cb436b346ba76eafd5f7afd2f370f9c3 Mon Sep 17 00:00:00 2001 From: SimonC Date: Tue, 2 Dec 2025 16:20:57 +0100 Subject: [PATCH 27/29] Feat/remove ignored qualifiers (#38) * Removed ignored qualifiers from function definitions * Bumped up version * Added cppcheck workflow * Fixed cppcheck warnings * Renamed workflow * Added more cppcheck options * Potential fix for code scanning alert no. 8: Workflow does not contain permissions Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> * Update src/CanDriver.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update .github/workflows/cmake_cppcheck.yml Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .github/workflows/cmake_cppcheck.yml | 40 +++++++++++++++++++ CMakeLists.txt | 2 +- include/CanDriver.hpp | 3 +- include/TlOptional.hpp | 2 +- include/exceptions/CanException.hpp | 2 +- include/exceptions/InvalidSocketException.hpp | 2 +- src/CanDriver.cpp | 13 ++++++ 7 files changed, 59 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/cmake_cppcheck.yml diff --git a/.github/workflows/cmake_cppcheck.yml b/.github/workflows/cmake_cppcheck.yml new file mode 100644 index 0000000..6c75806 --- /dev/null +++ b/.github/workflows/cmake_cppcheck.yml @@ -0,0 +1,40 @@ +name: CppCheck +permissions: + contents: read + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: Install system dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt-get update + sudo apt-get -y install can-utils libsocketcan-dev cppcheck + + - uses: actions/checkout@v3 + + - name: Configure CMake + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: CppCheck + # Build your program with the given configuration + run: cppcheck --project=${{github.workspace}}/build/compile_commands.json --check-level=exhaustive --enable=all --inconclusive --force --inline-suppr --quiet + diff --git a/CMakeLists.txt b/CMakeLists.txt index 81ee852..8ec238c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.7.2 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.7.3 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/include/CanDriver.hpp b/include/CanDriver.hpp index 519ae15..b4aca1a 100644 --- a/include/CanDriver.hpp +++ b/include/CanDriver.hpp @@ -94,7 +94,7 @@ namespace sockcanpp { CanDriver(const string& canInterface, const int32_t canProtocol, const int32_t filterMask, const CanId defaultSenderId = 0); CanDriver(const string& canInterface, const int32_t canProtocol, const filtermap_t& filters, const CanId defaultSenderId = 0); CanDriver() = default; - virtual ~CanDriver() { uninitialiseSocketCan(); } //!< Destructor + virtual ~CanDriver() { uninitialiseSocketCanNoThrow(); } //!< Destructor public: // +++ Getter / Setter +++ CanDriver& setDefaultSenderId(const CanId& id) { this->m_defaultSenderId = id; return *this; } //!< Sets the default sender ID @@ -158,6 +158,7 @@ namespace sockcanpp { private: // +++ Socket Management +++ void initialiseSocketCan(); //!< Initialises socketcan void uninitialiseSocketCan(); //!< Uninitialises socketcan + void uninitialiseSocketCanNoThrow() noexcept; //!< Uninitialises socketcan without throwing exceptions private: // +++ Member Functions +++ virtual CanMessage readMessageLock(bool const lock = true); //!< readMessage deadlock guard diff --git a/include/TlOptional.hpp b/include/TlOptional.hpp index 2b48c45..07b3f93 100644 --- a/include/TlOptional.hpp +++ b/include/TlOptional.hpp @@ -1977,7 +1977,7 @@ template class optional { /// /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise /// resets the stored value in `*this`. - template optional &operator=(const optional &rhs) noexcept { + template optional &operator=(const optional &rhs) { m_value = std::addressof(rhs.value()); return *this; } diff --git a/include/exceptions/CanException.hpp b/include/exceptions/CanException.hpp index 4a7d11b..b1ff8bf 100644 --- a/include/exceptions/CanException.hpp +++ b/include/exceptions/CanException.hpp @@ -45,7 +45,7 @@ namespace sockcanpp { namespace exceptions { const char* what() const noexcept { return m_message.c_str(); } public: // +++ Getter +++ - const int32_t getSocket() const { return m_socket; } + int32_t getSocket() const { return m_socket; } private: int32_t m_socket; diff --git a/include/exceptions/InvalidSocketException.hpp b/include/exceptions/InvalidSocketException.hpp index fb658c3..a1da36d 100644 --- a/include/exceptions/InvalidSocketException.hpp +++ b/include/exceptions/InvalidSocketException.hpp @@ -45,7 +45,7 @@ namespace sockcanpp { namespace exceptions { const char* what() const noexcept { return m_message.c_str(); } public: // +++ Getter +++ - const int32_t getSocket() const { return m_socket; } + int32_t getSocket() const { return m_socket; } private: int32_t m_socket; diff --git a/src/CanDriver.cpp b/src/CanDriver.cpp index 46ff7d5..7ebd83e 100644 --- a/src/CanDriver.cpp +++ b/src/CanDriver.cpp @@ -559,6 +559,19 @@ namespace sockcanpp { m_socketFd = -1; } + + /** + * @brief Closes the underlying CAN socket without throwing exceptions. + */ + void CanDriver::uninitialiseSocketCanNoThrow() noexcept { + try { + uninitialiseSocketCan(); + } catch (const CanCloseException& e) { + #ifdef libsockcanpp_ENABLE_OUTPUT_ON_CLOSE_EXCEPTIONS + std::cerr << "Warning: CanCloseException caught while uninitialising CAN socket: " << e.what() << std::endl; + #endif // libsockcanpp_ENABLE_OUTPUT_ON_CLOSE_EXCEPTIONS + } + } #pragma endregion } // namespace sockcanpp From 4509e1e607015df7fe55a67cf4c3cc2b3f5d7591 Mon Sep 17 00:00:00 2001 From: TanShaochang <30321432+petertheprocess@users.noreply.github.com> Date: Thu, 4 Dec 2025 23:09:05 +0100 Subject: [PATCH 28/29] Export can_errors headers and fix cmake install naming issue. (#39) * 1.export can_errors headers; 2.add lib prefix in install cmd to match cmake/libsockcanppConfig.cmake * Bumped up version --------- Co-authored-by: Shaochang TAN Co-authored-by: Simon Cahill --- CMakeLists.txt | 4 ++-- include/CMakeLists.txt | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ec238c..bee321d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.7.3 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.7.4 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) @@ -58,7 +58,7 @@ install(TARGETS ${PROJECT_NAME} ) install(EXPORT ${PROJECT_NAME}Targets - FILE ${PROJECT_NAME}Targets.cmake + FILE "lib${PROJECT_NAME}Targets.cmake" NAMESPACE ${PROJECT_NAME}:: DESTINATION lib/cmake/${PROJECT_NAME} ) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index c4858a4..2ae47b1 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -11,6 +11,9 @@ target_sources(${PROJECT_NAME} exceptions/CanException.hpp exceptions/CanInitException.hpp exceptions/InvalidSocketException.hpp + can_errors/CanControllerError.hpp + can_errors/CanProtocolError.hpp + can_errors/CanTransceiverError.hpp ) if (TARGET sockcanpp_test) @@ -25,5 +28,8 @@ if (TARGET sockcanpp_test) exceptions/CanException.hpp exceptions/CanInitException.hpp exceptions/InvalidSocketException.hpp + can_errors/CanControllerError.hpp + can_errors/CanProtocolError.hpp + can_errors/CanTransceiverError.hpp ) endif() From 8d3f5701e84b6ea84c89978d89ea3b7af859ff3a Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Dec 2025 21:21:26 +0100 Subject: [PATCH 29/29] Fix compilation on Linux kernels < 5.11 by using can_dlc instead of len (#41) * Initial plan * Fix compilation error on Linux kernels < 5.11 by using can_dlc instead of len Co-authored-by: SimonCahill <3124521+SimonCahill@users.noreply.github.com> * Complete fix for can_frame compatibility issue Co-authored-by: SimonCahill <3124521+SimonCahill@users.noreply.github.com> * Add CodeQL build artifacts to .gitignore Co-authored-by: SimonCahill <3124521+SimonCahill@users.noreply.github.com> * Added new GH workflows to test multiple distributions (and versions) * Hopefully fixed Debian package update issue. * Removed .10 Ubuntu versions * Added persissions; removed arch build. I'm not running anything from AUR. * One last attempt to build for old Debian * Manual CMake install * Remove extra trust * Added wget to list of Debian packages to install * Added conditional header include if bitcast is supported. * Added missing wget to Debian package * Removed more arch * Fucking Docker. * Attempting to fix Ubuntu repos * I hate YAML. * Added missing package dependency for Debian * Fixed weird issue with containerised cpack? * Added missing dnf packages * 18.04 fails to checkout * Corrected Fedora RPM build? * Removed 18.04; disabled deb repo stuff * I don't know which versions use old-release and which use archive. I'm very glad when this whole thing is squashed down and nobody sees my incompetence. * I think I now have the repos correct * Wrong file * 19/20.04 are in archive.ubuntu * Ubuntu now downloads the correct cmake version * 22.04 is also archived * Fixed weirdness caused by containers when calling cpack * Hopefully fixed it now. I hate distro management. * Added missing RPM package * Bumped up version --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: SimonCahill <3124521+SimonCahill@users.noreply.github.com> Co-authored-by: Simon Cahill --- .github/workflows/cmake.yml | 3 + .github/workflows/cmake_debian_versions.yml | 77 ++++++++++++++++++ .github/workflows/cmake_fedora_versions.yml | 56 ++++++++++++++ .github/workflows/cmake_no_concepts.yml | 3 + .github/workflows/cmake_ubuntu_versions.yml | 85 ++++++++++++++++++++ .github/workflows/cpack.yml | 3 + .github/workflows/cpack_debian_versions.yml | 81 +++++++++++++++++++ .github/workflows/cpack_fedora_versions.yml | 62 +++++++++++++++ .github/workflows/cpack_no_concepts.yml | 3 + .github/workflows/cpack_ubuntu_versions.yml | 86 +++++++++++++++++++++ .github/workflows/gtest.yml | 3 + .github/workflows/gtest_no_concepts.yml | 3 + .gitignore | 2 + CMakeLists.txt | 2 +- include/CanMessage.hpp | 6 +- 15 files changed, 472 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/cmake_debian_versions.yml create mode 100644 .github/workflows/cmake_fedora_versions.yml create mode 100644 .github/workflows/cmake_ubuntu_versions.yml create mode 100644 .github/workflows/cpack_debian_versions.yml create mode 100644 .github/workflows/cpack_fedora_versions.yml create mode 100644 .github/workflows/cpack_ubuntu_versions.yml diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index e880adb..11455cd 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Debug diff --git a/.github/workflows/cmake_debian_versions.yml b/.github/workflows/cmake_debian_versions.yml new file mode 100644 index 0000000..092d44c --- /dev/null +++ b/.github/workflows/cmake_debian_versions.yml @@ -0,0 +1,77 @@ +name: Build Debug (Debian Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Debian 12 (Bookworm) + image: debian:12 + version: "12" + - name: Debian 11 (Bullseye) + image: debian:11 + version: "11" + - name: Debian 10 (Buster) + image: debian:10 + version: "10" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "debian" ]]; then + case "${VERSION_ID}" in + 8*|9*|10*) + sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list + sed -i '/security.debian.org/d' /etc/apt/sources.list + if [[ -n "${VERSION_CODENAME:-}" ]]; then + printf '\ndeb http://archive.debian.org/debian-security %s/updates main contrib non-free\n' "${VERSION_CODENAME}" >> /etc/apt/sources.list + fi + printf "\n\nAcquire::Check-Valid-Until \"false\";\n" >> /etc/apt/apt.conf + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/cmake_fedora_versions.yml b/.github/workflows/cmake_fedora_versions.yml new file mode 100644 index 0000000..18d34ed --- /dev/null +++ b/.github/workflows/cmake_fedora_versions.yml @@ -0,0 +1,56 @@ +name: Build Debug (Fedora Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Fedora 41 + image: fedora:41 + - name: Fedora 40 + image: fedora:40 + - name: Fedora 39 + image: fedora:39 + + steps: + - name: Prepare environment + shell: bash + run: | + set -euo pipefail + dnf -y update + dnf -y install \ + cmake \ + gcc \ + gcc-c++ \ + make \ + git \ + pkgconf-pkg-config \ + can-utils \ + libsocketcan \ + libsocketcan-devel + dnf clean all + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/cmake_no_concepts.yml b/.github/workflows/cmake_no_concepts.yml index bf6d792..439d76c 100644 --- a/.github/workflows/cmake_no_concepts.yml +++ b/.github/workflows/cmake_no_concepts.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Debug diff --git a/.github/workflows/cmake_ubuntu_versions.yml b/.github/workflows/cmake_ubuntu_versions.yml new file mode 100644 index 0000000..c4649ae --- /dev/null +++ b/.github/workflows/cmake_ubuntu_versions.yml @@ -0,0 +1,85 @@ +name: Build Debug (Ubuntu Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Debug + +jobs: + build: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: CMake Ubuntu 25.04 + image: ubuntu:25.04 + version: "25.04" + - name: CMake Ubuntu 24.04 + image: ubuntu:24.04 + version: "24.04" + - name: CMake Ubuntu 23.04 + image: ubuntu:23.04 + version: "23.04" + - name: CMake Ubuntu 22.04 + image: ubuntu:22.04 + version: "22.04" + - name: CMake Ubuntu 21.04 + image: ubuntu:21.04 + version: "21.04" + - name: CMake Ubuntu 20.04 + image: ubuntu:20.04 + version: "20.04" + - name: CMake Ubuntu 19.04 + image: ubuntu:19.04 + version: "19.04" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "ubuntu" ]]; then + case "${VERSION_ID}" in + 19.04|21.04|23.04) + sed -i 's|http://archive.ubuntu.com/ubuntu/|http://old-releases.ubuntu.com/ubuntu/|g' /etc/apt/sources.list + sed -i 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' /etc/apt/sources.list + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} diff --git a/.github/workflows/cpack.yml b/.github/workflows/cpack.yml index 40dc4c7..b9f3c97 100644 --- a/.github/workflows/cpack.yml +++ b/.github/workflows/cpack.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release diff --git a/.github/workflows/cpack_debian_versions.yml b/.github/workflows/cpack_debian_versions.yml new file mode 100644 index 0000000..5a65195 --- /dev/null +++ b/.github/workflows/cpack_debian_versions.yml @@ -0,0 +1,81 @@ +name: Build and Pack (Debian Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Release + +jobs: + build-and-pack: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Debian 12 (Bookworm) + image: debian:12 + version: "12" + - name: Debian 11 (Bullseye) + image: debian:11 + version: "11" + - name: Debian 10 (Buster) + image: debian:10 + version: "10" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "debian" ]]; then + case "${VERSION_ID}" in + 8*|9*|10*) + sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list + sed -i '/security.debian.org/d' /etc/apt/sources.list + if [[ -n "${VERSION_CODENAME:-}" ]]; then + printf '\ndeb http://archive.debian.org/debian-security %s/updates main contrib non-free\n' "${VERSION_CODENAME}" >> /etc/apt/sources.list + fi + echo 'Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/99disable-check-valid-until + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget \ + rpm + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + cd ${{github.workspace}}/build + cpack . diff --git a/.github/workflows/cpack_fedora_versions.yml b/.github/workflows/cpack_fedora_versions.yml new file mode 100644 index 0000000..08a9286 --- /dev/null +++ b/.github/workflows/cpack_fedora_versions.yml @@ -0,0 +1,62 @@ +name: Build and Pack (Fedora Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Release + +jobs: + build-and-pack: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: Fedora 41 + image: fedora:41 + - name: Fedora 40 + image: fedora:40 + - name: Fedora 39 + image: fedora:39 + + steps: + - name: Prepare environment + shell: bash + run: | + set -euo pipefail + dnf -y update + dnf -y install \ + cmake \ + gcc \ + gcc-c++ \ + make \ + git \ + pkgconf-pkg-config \ + can-utils \ + libsocketcan \ + libsocketcan-devel \ + wget \ + rpm-build + + dnf clean all + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + cd ${{github.workspace}}/build + cpack . diff --git a/.github/workflows/cpack_no_concepts.yml b/.github/workflows/cpack_no_concepts.yml index 5c785ed..d8b6208 100644 --- a/.github/workflows/cpack_no_concepts.yml +++ b/.github/workflows/cpack_no_concepts.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release diff --git a/.github/workflows/cpack_ubuntu_versions.yml b/.github/workflows/cpack_ubuntu_versions.yml new file mode 100644 index 0000000..70aff31 --- /dev/null +++ b/.github/workflows/cpack_ubuntu_versions.yml @@ -0,0 +1,86 @@ +name: Build and Pack (Ubuntu Versions) + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +env: + BUILD_TYPE: Release + +jobs: + build-and-pack: + name: ${{ matrix.name }} + runs-on: ubuntu-latest + container: + image: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + include: + - name: CPack Ubuntu 24.04 + image: ubuntu:24.04 + version: "24.04" + - name: CPack Ubuntu 23.04 + image: ubuntu:23.04 + version: "23.04" + - name: CPack Ubuntu 22.04 + image: ubuntu:22.04 + version: "22.04" + - name: CPack Ubuntu 21.04 + image: ubuntu:21.04 + version: "21.04" + - name: CPack Ubuntu 20.04 + image: ubuntu:20.04 + version: "20.04" + - name: CPack Ubuntu 19.04 + image: ubuntu:19.04 + version: "19.04" + + steps: + - name: Prepare environment + shell: bash + env: + DEBIAN_FRONTEND: noninteractive + run: | + set -euo pipefail + if [[ -f /etc/os-release ]]; then + . /etc/os-release + if [[ "${ID}" == "ubuntu" ]]; then + case "${VERSION_ID}" in + 19.04|21.04|23.04) + sed -i 's|http://archive.ubuntu.com/ubuntu/|http://old-releases.ubuntu.com/ubuntu/|g' /etc/apt/sources.list + sed -i 's|http://security.ubuntu.com/ubuntu|http://old-releases.ubuntu.com/ubuntu|g' /etc/apt/sources.list + ;; + esac + fi + fi + apt-get update + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ + git \ + pkg-config \ + can-utils \ + libsocketcan-dev \ + wget \ + rpm + + wget https://github.com/Kitware/CMake/releases/download/v3.31.10/cmake-3.31.10-linux-x86_64.sh -O /tmp/cmake-installer.sh + chmod +x /tmp/cmake-installer.sh + /tmp/cmake-installer.sh --skip-license --prefix=/usr/local + + - uses: actions/checkout@v4 + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + + - name: Build + run: | + cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + cd ${{github.workspace}}/build + cpack . diff --git a/.github/workflows/gtest.yml b/.github/workflows/gtest.yml index 8e8cc04..bddc5fe 100644 --- a/.github/workflows/gtest.yml +++ b/.github/workflows/gtest.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Debug diff --git a/.github/workflows/gtest_no_concepts.yml b/.github/workflows/gtest_no_concepts.yml index 8131a9b..099e146 100644 --- a/.github/workflows/gtest_no_concepts.yml +++ b/.github/workflows/gtest_no_concepts.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ "master" ] +permissions: + contents: read + env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Debug diff --git a/.gitignore b/.gitignore index f27cdbf..aad91be 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,8 @@ # Build directories ### build*/ +_codeql_build_dir/ +_codeql_detected_source_root ### # Documentation (Doxygen) diff --git a/CMakeLists.txt b/CMakeLists.txt index bee321d..63f0f14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.23) -project(sockcanpp LANGUAGES CXX VERSION 1.7.4 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") +project(sockcanpp LANGUAGES CXX VERSION 1.7.5 DESCRIPTION "A simple C++ wrapper for the Linux SocketCAN API" HOMEPAGE_URL "https://github.com/simoncahill/libsockcanpp") option(BUILD_SHARED_LIBS "Build shared libraries (.so) instead of static ones (.a)" ON) option(BUILD_TESTS "Build the tests" OFF) diff --git a/include/CanMessage.hpp b/include/CanMessage.hpp index abde778..aab2fd7 100644 --- a/include/CanMessage.hpp +++ b/include/CanMessage.hpp @@ -28,7 +28,9 @@ ////////////////////////////// #include +#if __cpp_lib_bit_cast >= 201806L #include +#endif // __cpp_lib_bit_cast >= 201806L #include #include #if __cpp_lib_span >= 201907L @@ -117,7 +119,7 @@ namespace sockcanpp { m_rawFrame.can_id = canId; std::copy(frameData.begin(), frameData.end(), m_rawFrame.data); - m_rawFrame.len = frameData.size(); + m_rawFrame.can_dlc = frameData.size(); } explicit CanMessageT(const CanId& canId, const std::span& frameData, const Duration& timestampOffset): CanMessageT(canId, frameData) { @@ -137,7 +139,7 @@ namespace sockcanpp { const string getFrameData() const noexcept { string data{}; - data.reserve(m_rawFrame.len); + data.reserve(m_rawFrame.can_dlc); std::copy(std::begin(m_rawFrame.data), std::begin(m_rawFrame.data) + m_rawFrame.can_dlc, std::back_inserter(data)); return data;