├── .gitignore ├── .gitmodules ├── test ├── example │ ├── CMakeLists.txt │ ├── etl_profile.h │ └── main.cc ├── socket-test │ ├── CMakeLists.txt │ ├── etl_profile.h │ └── main.cc └── unit-test │ ├── 00CatchInit.cc │ ├── etl_profile.h │ ├── 03CanFrameTest.cc │ ├── CMakeLists.txt │ ├── 06BsAndStTest.cc │ ├── 07IsoMessageTest.cc │ ├── 08CallbackTest.cc │ ├── 04CrosswiseTest.cc │ ├── 02SendTest.cc │ ├── 01RecvTest.cc │ └── 05AddressTest.cc ├── CMakeLists.txt ├── src ├── arduino │ ├── ArduinoTimeProvider.h │ ├── ArduinoExceptionHandler.h │ ├── ArduinoMcp2515TransportProtocol.h │ └── ArduinoMcp2515Can.h ├── StlTypes.h ├── LinuxTransportProtocol.h ├── CppCompat.h ├── MiscTypes.h ├── CanFrame.h ├── LinuxCanFrame.h ├── Address.h └── TransportProtocol.h ├── COPYING ├── .clang-tidy └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeLists.txt.user 2 | build-example* 3 | build-cpp-can* 4 | .vscode 5 | build/ 6 | .clangd 7 | compile_commands.json 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/etl"] 2 | path = deps/etl 3 | url = git@github.com:ETLCPP/etl.git 4 | [submodule "deps/GSL"] 5 | path = deps/GSL 6 | url = git@github.com:microsoft/GSL.git 7 | [submodule "deps/fmt"] 8 | path = deps/fmt 9 | url = git@github.com:fmtlib/fmt.git 10 | [submodule "deps/Catch2"] 11 | path = deps/Catch2 12 | url = https://github.com/catchorg/Catch2.git 13 | -------------------------------------------------------------------------------- /test/example/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 2 | SET (CMAKE_VERBOSE_MAKEFILE OFF) 3 | 4 | # hardcoded! 5 | SET (CMAKE_BUILD_TYPE Debug) 6 | SET(CMAKE_C_FLAGS "-std=gnu99 -Wall" CACHE INTERNAL "c compiler flags") 7 | SET(CMAKE_CXX_FLAGS "-std=c++17 -Wall" CACHE INTERNAL "cxx compiler flags") 8 | ADD_DEFINITIONS ("-DUNIT_TEST") 9 | include_directories("./") 10 | 11 | ADD_EXECUTABLE(example 12 | "main.cc" 13 | "../../src/TransportProtocol.h" 14 | ) 15 | 16 | -------------------------------------------------------------------------------- /test/socket-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 2 | SET (CMAKE_VERBOSE_MAKEFILE OFF) 3 | 4 | SET (CMAKE_BUILD_TYPE Debug) 5 | SET(CMAKE_CXX_FLAGS "-std=c++17 -Wall" CACHE INTERNAL "cxx compiler flags") 6 | include_directories("./") 7 | 8 | ADD_EXECUTABLE(socket-test 9 | "../../src/TransportProtocol.h" 10 | "../../src/CanFrame.h" 11 | "../../src/Address.h" 12 | "../../src/MiscTypes.h" 13 | 14 | "main.cc" 15 | 16 | "../../deps/fmt/src/format.cc" 17 | ) 18 | 19 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED (VERSION 3.5 FATAL_ERROR) 2 | 3 | if(COMMAND cmake_policy) 4 | cmake_policy(SET CMP0003 NEW) 5 | endif(COMMAND cmake_policy) 6 | 7 | if(COMMAND cmake_policy) 8 | cmake_policy(SET CMP0048 NEW) 9 | endif(COMMAND cmake_policy) 10 | 11 | PROJECT (cpp-can-isotp) 12 | SET (CMAKE_CXX_FLAGS "-std=c++17 -Wall -fsanitize=address -fno-omit-frame-pointer" CACHE INTERNAL "cxx compiler flags") 13 | INCLUDE_DIRECTORIES("src" "deps/GSL/include" "deps/etl/include" "deps/fmt/include" "deps/Catch2/single_include") 14 | add_subdirectory (test/example) 15 | add_subdirectory (test/unit-test) 16 | add_subdirectory (test/socket-test) 17 | 18 | -------------------------------------------------------------------------------- /test/unit-test/00CatchInit.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file 10 | #include 11 | 12 | -------------------------------------------------------------------------------- /test/example/etl_profile.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #define ETL_LOG_ERRORS 11 | #define ETL_VERBOSE_ERRORS 12 | #define ETL_CHECK_PUSH_POP 13 | 14 | #include "etl/profiles/cpp17.h" 15 | 16 | -------------------------------------------------------------------------------- /test/socket-test/etl_profile.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #define ETL_LOG_ERRORS 11 | #define ETL_VERBOSE_ERRORS 12 | #define ETL_CHECK_PUSH_POP 13 | 14 | #include "etl/profiles/cpp17.h" 15 | 16 | -------------------------------------------------------------------------------- /test/unit-test/etl_profile.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #define ETL_LOG_ERRORS 11 | #define ETL_VERBOSE_ERRORS 12 | #define ETL_CHECK_PUSH_POP 13 | 14 | #include "etl/profiles/cpp17.h" 15 | 16 | -------------------------------------------------------------------------------- /test/unit-test/03CanFrameTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | 12 | using namespace tp; 13 | 14 | TEST_CASE ("instantiate", "[canFrame]") 15 | { 16 | CanFrame cf{0x00, true}; 17 | REQUIRE (cf.dlc == 0); 18 | } 19 | -------------------------------------------------------------------------------- /src/arduino/ArduinoTimeProvider.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include 11 | 12 | namespace tp { 13 | 14 | /** 15 | * @brief The ArduinoTimeProvider struct 16 | */ 17 | struct ArduinoTimeProvider { 18 | long operator() () const { return millis (); } 19 | }; 20 | 21 | } // namespace tp 22 | -------------------------------------------------------------------------------- /src/arduino/ArduinoExceptionHandler.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | 13 | namespace tp { 14 | 15 | const char ARDUINO_ERROR_STR[] PROGMEM = "CAN ISO TP erorr : "; 16 | 17 | struct ArduinoExceptionHandler { 18 | template void operator() (T const &error) 19 | { 20 | Serial.print (ARDUINO_ERROR_STR); 21 | Serial.println (int (error)); 22 | } 23 | }; 24 | 25 | } // namespace tp 26 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright 2021 Łukasz Iwaszkiewicz 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/unit-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | CMAKE_MINIMUM_REQUIRED(VERSION 3.5) 2 | SET (CMAKE_VERBOSE_MAKEFILE OFF) 3 | 4 | SET (CMAKE_BUILD_TYPE Debug) 5 | 6 | # TODO can't get it to work with ninja... 7 | SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall") 8 | SET (CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall") 9 | # SET (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -fsanitize=address -fsanitize=leak") 10 | # SET (CMAKE_C_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17 -Wall -fsanitize=address -fsanitize=leak") 11 | # SET (CMAKE_EXE_LINKER_FLAGS:STRING "-fsanitize=address -fsanitize=leak") 12 | # SET (CMAKE_MODULE_LINKER_FLAGS:STRING "-fsanitize=address -fsanitize=leak") 13 | 14 | include_directories("./") 15 | 16 | ENABLE_TESTING () 17 | 18 | ADD_DEFINITIONS ("-DUNIT_TEST=1") 19 | ADD_EXECUTABLE(unit-test 20 | "../../src/Address.h" 21 | "../../src/CanFrame.h" 22 | "../../src/CppCompat.h" 23 | "../../src/LinuxCanFrame.h" 24 | "../../src/LinuxTransportProtocol.h" 25 | "../../src/MiscTypes.h" 26 | "../../src/StlTypes.h" 27 | "../../src/TransportProtocol.h" 28 | 29 | "etl_profile.h" 30 | "00CatchInit.cc" 31 | "01RecvTest.cc" 32 | "02SendTest.cc" 33 | "03CanFrameTest.cc" 34 | "04CrosswiseTest.cc" 35 | "05AddressTest.cc" 36 | "06BsAndStTest.cc" 37 | "07IsoMessageTest.cc" 38 | "08CallbackTest.cc" 39 | ) 40 | 41 | ADD_TEST (unit-test unit-test) 42 | -------------------------------------------------------------------------------- /src/StlTypes.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace tp { 17 | class CanFrame; 18 | 19 | /** 20 | * @brief The TimeProvider struct 21 | * TODO if I'm not mistaken, those classes ought to have 100µs resolution instead of 1ms (1000µs). 22 | */ 23 | struct ChronoTimeProvider { 24 | long operator() () const 25 | { 26 | using namespace std::chrono; 27 | system_clock::time_point now = system_clock::now (); 28 | auto duration = now.time_since_epoch (); 29 | return duration_cast (duration).count (); 30 | } 31 | }; 32 | 33 | struct CoutPrinter { 34 | template void operator() (T &&a) { std::cout << std::forward (a) << std::endl; } 35 | }; 36 | 37 | /** 38 | * Buffer for all input and output operations 39 | */ 40 | using IsoMessage = std::vector; 41 | 42 | } // namespace tp 43 | -------------------------------------------------------------------------------- /src/LinuxTransportProtocol.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "LinuxCanFrame.h" 11 | #include "StlTypes.h" 12 | #include "TransportProtocol.h" 13 | 14 | namespace tp { 15 | 16 | template 19 | auto create (Address const &myAddress, CallbackT callback, CanOutputInterfaceT outputInterface = {}, TimeProviderT timeProvider = {}, 20 | ExceptionHandlerT errorHandler = {}) 21 | { 22 | using TP = TransportProtocol>; 24 | 25 | return TP{myAddress, callback, outputInterface, timeProvider, errorHandler}; 26 | } 27 | 28 | } // namespace tp 29 | -------------------------------------------------------------------------------- /src/arduino/ArduinoMcp2515TransportProtocol.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "ArduinoExceptionHandler.h" 11 | #include "ArduinoMcp2515Can.h" 12 | #include "ArduinoTimeProvider.h" 13 | #include "TransportProtocol.h" 14 | 15 | namespace tp { 16 | 17 | template 20 | auto create (Address const &myAddress, CallbackT callback, CanOutputInterfaceT outputInterface, TimeProviderT timeProvider = {}, 21 | ExceptionHandlerT errorHandler = {}) 22 | { 23 | using TP = TransportProtocol>; 25 | 26 | return TP{myAddress, callback, outputInterface, timeProvider, errorHandler}; 27 | } 28 | 29 | } // namespace tp 30 | -------------------------------------------------------------------------------- /src/CppCompat.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | /** 16 | * This file is for maintaining compatibility with systems where C++ standard library is 17 | * not available i.e. for Arduino. I was totally unaware, that there is no C++ standard 18 | * library implemented for this toy platform. 19 | */ 20 | 21 | #if defined(__GNUC__) && (defined(AVR) || defined(ARDUINO) || defined (ARDUINO_ARCH_AVR)) 22 | #include 23 | #include 24 | #include 25 | 26 | #if !defined Expects 27 | #define Expects(x) assert (x) 28 | #endif 29 | 30 | namespace gsl { 31 | 32 | template constexpr T &at (T (&arr)[N], const int i) 33 | { 34 | Expects (i >= 0 && i < static_cast (N)); 35 | return arr[static_cast (i)]; 36 | } 37 | 38 | template constexpr auto at (Cont &cont, const int i) -> decltype (cont[cont.size ()]) 39 | { 40 | Expects (i >= 0 && i < static_cast (cont.size ())); 41 | using size_type = decltype (cont.size ()); 42 | return cont[static_cast (i)]; 43 | } 44 | } // namespace gsl 45 | 46 | namespace std { 47 | template _Up __declval (int); 48 | 49 | template _Tp __declval (long); 50 | 51 | template auto declval () noexcept -> decltype (__declval<_Tp> (0)); 52 | 53 | template struct __declval_protector { 54 | static const bool __stop = false; 55 | }; 56 | 57 | template auto declval () noexcept -> decltype (__declval<_Tp> (0)) 58 | { 59 | static_assert (__declval_protector<_Tp>::__stop, "declval() must not be used!"); 60 | return __declval<_Tp> (0); 61 | } 62 | } // namespace std 63 | #else 64 | #include 65 | #include 66 | #endif 67 | -------------------------------------------------------------------------------- /test/example/main.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | 12 | std::ostream &operator<< (std::ostream &o, tp::IsoMessage const &b); 13 | std::ostream &operator<< (std::ostream &o, tp::CanFrame const &cf); 14 | 15 | /****************************************************************************/ 16 | 17 | int main () 18 | { 19 | auto tp = tp::create ( 20 | tp::Address (0x12, 0x34), [] (auto &&tm) { std::cout << "TransportMessage : " << tm; }, 21 | [] (auto &&canFrame) { 22 | std::cout << "CAN Tx : " << canFrame << std::endl; 23 | return true; 24 | }, 25 | tp::ChronoTimeProvider{}, [] (auto &&error) { std::cout << "Error : " << uint32_t (error) << std::endl; }); 26 | 27 | tp.onCanNewFrame (tp::CanFrame (0x00, true, 1, 0x01, 0x67)); 28 | } 29 | 30 | /****************************************************************************/ 31 | 32 | std::ostream &operator<< (std::ostream &o, tp::IsoMessage const &b) 33 | { 34 | o << "["; 35 | 36 | for (auto i = b.cbegin (); i != b.cend ();) { 37 | 38 | o << std::hex << int (*i); 39 | 40 | if (++i != b.cend ()) { 41 | o << ","; 42 | } 43 | } 44 | 45 | o << "]"; 46 | return o; 47 | } 48 | 49 | /*****************************************************************************/ 50 | 51 | std::ostream &operator<< (std::ostream &o, tp::CanFrame const &cf) 52 | { 53 | o << "CanFrame id = " << cf.id << ", dlc = " << int (cf.dlc) << ", ext = " << cf.extended << ", data = ["; 54 | 55 | for (int i = 0; i < cf.dlc;) { 56 | o << int (gsl::at (cf.data, i)); 57 | 58 | if (++i != cf.dlc) { 59 | o << ","; 60 | } 61 | else { 62 | break; 63 | } 64 | } 65 | 66 | o << "]"; 67 | return o; 68 | } 69 | 70 | /****************************************************************************/ 71 | -------------------------------------------------------------------------------- /src/arduino/ArduinoMcp2515Can.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "CanFrame.h" 11 | #include 12 | 13 | namespace tp { 14 | 15 | /** 16 | * This library uses exactly the same format as Linux does. 17 | */ 18 | template <> class CanFrameWrapper { 19 | public: 20 | CanFrameWrapper () = default; 21 | explicit CanFrameWrapper (can_frame const &cf) : frame (cf) {} /// Construct from underlying implementation type. 22 | 23 | template void setData (int totalElements, A c1, B... cr) 24 | { 25 | static_assert (sizeof...(cr) <= 7); 26 | set (totalElements - 1 - sizeof...(cr), c1); 27 | 28 | if constexpr (sizeof...(cr) > 0) { 29 | setData (totalElements, cr...); 30 | } 31 | } 32 | 33 | template CanFrameWrapper (uint32_t id, bool extended, T... data) 34 | { 35 | setId (id); 36 | setExtended (extended); 37 | setData (sizeof...(data), data...); 38 | } 39 | 40 | template static can_frame create (uint32_t id, bool extended, T... data) { return can_frame{id, extended, data...}; } 41 | 42 | can_frame const &value () const { return frame; } 43 | 44 | uint32_t getId () const { return (isExtended ()) ? (frame.can_id & CAN_EFF_MASK) : (frame.can_id & CAN_SFF_MASK); } 45 | void setId (uint32_t i) { frame.can_id = i; } 46 | 47 | bool isExtended () const { return bool (frame.can_id & CAN_EFF_FLAG); } 48 | void setExtended (bool b) 49 | { 50 | if (b) { 51 | frame.can_id |= CAN_EFF_FLAG; 52 | } 53 | else { 54 | frame.can_id &= ~CAN_EFF_FLAG; 55 | } 56 | } 57 | 58 | uint8_t getDlc () const { return frame.can_dlc; } 59 | void setDlc (uint8_t d) { frame.can_dlc = d; } 60 | 61 | uint8_t get (size_t i) const { return gsl::at (frame.data, i); } 62 | void set (size_t i, uint8_t b) { gsl::at (frame.data, i) = b; } 63 | 64 | private: 65 | can_frame frame{}; 66 | }; 67 | 68 | } // namespace tp 69 | -------------------------------------------------------------------------------- /src/MiscTypes.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "CppCompat.h" 11 | 12 | namespace tp { 13 | 14 | /** 15 | * Codes signaled to the user via indication and confirmation callbacks. 16 | */ 17 | enum class Result { 18 | // standard result codes 19 | N_OK, /// Service execution performed successfully. 20 | N_TIMEOUT_A, /// Timer N_Ar / N_As exceeded N_Asmax / N_Armax 21 | N_TIMEOUT_BS, /// N_Bs time exceeded N_Bsmax 22 | N_TIMEOUT_CR, /// N_Cr time exceeded N_Crmax 23 | N_WRONG_SN, /// Unexpected sequence number in incoming consecutive frame 24 | N_INVALID_FS, /// Invalid FlowStatus value received in flow control frame. 25 | N_UNEXP_PDU, /// Unexpected protocol data unit received. 26 | N_WFT_OVRN, /// This signals the user that we have received too many wait frames while awaiting a flow control frame. 27 | N_BUFFER_OVFLW, /// When receiving flow control with FlowStatus = OVFLW. Transmission is aborted. 28 | N_ERROR, /// General error. 29 | // implementation defined result codes 30 | N_MESSAGE_NUM_MAX /// Not a standard error. This one means that there is too many different isoMessages being assembled from multiple 31 | /// chunks of CAN frames now. 32 | 33 | }; 34 | 35 | /** 36 | * Status (mostly error) codes passed into the errorHandler. 37 | */ 38 | enum class Status { 39 | OK, /// No error 40 | ADDRESS_DECODE_ERROR, /// Address encoder was unable to decode an address from frame id. 41 | ADDRESS_ENCODE_ERROR, /// Address encoder was unable to encode an address into a frame id. 42 | SEND_FAILED /// Unable to send a can frame. 43 | }; 44 | 45 | /** 46 | * @brief The InfiniteLoop struct 47 | */ 48 | struct InfiniteLoop { 49 | template void operator() (T const & /*Error*/) 50 | { 51 | while (true) { 52 | } 53 | } 54 | }; 55 | 56 | /** 57 | * @brief The EmptyCallback struct 58 | */ 59 | struct EmptyCallback { 60 | template void operator() (T... /* a */) {} 61 | }; 62 | 63 | /// NPDU -> Network Protocol Data Unit 64 | enum class IsoNPduType { SINGLE_FRAME = 0, FIRST_FRAME = 1, CONSECUTIVE_FRAME = 2, FLOW_FRAME = 3 }; 65 | 66 | /// FS field in Flow Control Frame 67 | enum class FlowStatus { CONTINUE_TO_SEND = 0, WAIT = 1, OVERFLOWED = 2 }; 68 | 69 | } // namespace tp 70 | -------------------------------------------------------------------------------- /src/CanFrame.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "MiscTypes.h" 11 | 12 | namespace tp { 13 | 14 | /** 15 | * Default CAN message API. You can use it right out of the box or you can use some 16 | * other class among with a wrapper which you have to provide in sych a scenario. 17 | * This lets you integrate this transport protocol layer with existing link protocol 18 | * libraries. 19 | */ 20 | struct CanFrame { 21 | 22 | CanFrame () = default; 23 | 24 | template 25 | CanFrame (uint32_t id, bool extended, T... t) : id (id), extended (extended), data{uint8_t (t)...}, dlc (sizeof...(t)) 26 | { 27 | Expects ((id & 0xE0000000) == 0); 28 | } 29 | 30 | uint32_t id; 31 | bool extended; 32 | etl::array data; 33 | uint8_t dlc; 34 | }; 35 | 36 | template struct CanFrameWrapper { 37 | }; 38 | 39 | /** 40 | * This is a wrapper which helps to use this library with different 41 | * CAN bus implementations. You can reimplement (add your own specialisation) 42 | * of this template class. Wrappers were split into this CanFrameWrapperBase 43 | * and CanFrameWrapper so users didn't have to reimplement all of the 44 | * methods CanFrameWrapper provide. 45 | */ 46 | template <> class CanFrameWrapper { 47 | public: 48 | explicit CanFrameWrapper (CanFrame cf) : frame (std::move (cf)) {} /// Construct from underlying implementation type. 49 | template CanFrameWrapper (uint32_t id, bool extended, T... data) : frame (id, extended, data...) {} 50 | CanFrameWrapper () = default; 51 | 52 | template static CanFrame create (uint32_t id, bool extended, T... data) { return CanFrame{id, extended, data...}; } 53 | 54 | CanFrame const &value () const { return frame; } /// Transform to underlying implementation type. 55 | 56 | uint32_t getId () const { return frame.id; } /// Arbitration id. 57 | void setId (uint32_t i) { frame.id = i; } /// Arbitration id. 58 | 59 | bool isExtended () const { return frame.extended; } 60 | void setExtended (bool b) { frame.extended = b; } 61 | 62 | uint8_t getDlc () const { return frame.dlc; } 63 | void setDlc (uint8_t d) { frame.dlc = d; } 64 | 65 | uint8_t get (size_t i) const { return gsl::at (frame.data, i); } 66 | void set (size_t i, uint8_t b) { gsl::at (frame.data, i) = b; } 67 | 68 | private: 69 | // TODO this should be some kind of handler if we want to call this class a "wrapper". 70 | // This way we would get rid of one copy during reception of a CAN frame. 71 | // But at the other hand there must be a possibility to create new "wrappers" without 72 | // providing the internal object when sending frames. 73 | CanFrame frame{}; 74 | }; 75 | 76 | } // namespace tp 77 | -------------------------------------------------------------------------------- /src/LinuxCanFrame.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "CanFrame.h" 11 | #include 12 | 13 | namespace tp { 14 | 15 | /** 16 | * Wrapper for particular can frame type we are using in this program (namely 17 | * can_frame in this case). 18 | */ 19 | template <> class CanFrameWrapper { 20 | public: 21 | CanFrameWrapper () = default; 22 | explicit CanFrameWrapper (can_frame const &cf) : frame (cf) {} /// Construct from underlying implementation type. 23 | 24 | template void setData (int totalElements, A c1, B... cr) 25 | { 26 | static_assert (sizeof...(cr) <= 7); 27 | set (totalElements - 1 - sizeof...(cr), c1); 28 | 29 | if constexpr (sizeof...(cr) > 0) { 30 | setData (totalElements, cr...); 31 | } 32 | } 33 | 34 | template CanFrameWrapper (uint32_t id, bool extended, T... data) 35 | { 36 | setId (id); 37 | setExtended (extended); 38 | setData (sizeof...(data), data...); 39 | } 40 | 41 | template static can_frame create (uint32_t id, bool extended, T... data) { return can_frame{id, extended, data...}; } 42 | 43 | can_frame const &value () const { return frame; } 44 | 45 | uint32_t getId () const { return (isExtended ()) ? (frame.can_id & CAN_EFF_MASK) : (frame.can_id & CAN_SFF_MASK); } 46 | void setId (uint32_t i) { frame.can_id = i; } 47 | 48 | bool isExtended () const { return bool (frame.can_id & CAN_EFF_FLAG); } 49 | void setExtended (bool b) 50 | { 51 | if (b) { 52 | frame.can_id |= CAN_EFF_FLAG; 53 | } 54 | else { 55 | frame.can_id &= ~CAN_EFF_FLAG; 56 | } 57 | } 58 | 59 | uint8_t getDlc () const { return frame.can_dlc; } 60 | void setDlc (uint8_t d) { frame.can_dlc = d; } 61 | 62 | uint8_t get (size_t i) const { return gsl::at (frame.data, i); } 63 | void set (size_t i, uint8_t b) { gsl::at (frame.data, i) = b; } 64 | 65 | private: 66 | can_frame frame{}; 67 | }; 68 | 69 | /** 70 | * This interface assumes that sending single CAN frame is instant and we 71 | * know the status (whether it failed or succeeded) instantly (thus boolean) 72 | * return type. 73 | * 74 | * Important! If this interface fails, it should return false. Also, ISO requires, 75 | * that sending a CAN message should take not more as 1000ms + 50% i.e. 1500ms. If 76 | * this interface detects (internally) that, this time constraint wasn't met, it 77 | * also should return false. Those are N_As and N_Ar timeouts. 78 | */ 79 | struct LinuxCanOutputInterface { 80 | bool operator() (CanFrame const & /*unused*/) { return true; } 81 | }; 82 | 83 | } // namespace tp 84 | -------------------------------------------------------------------------------- /test/unit-test/06BsAndStTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | 12 | using namespace tp; 13 | 14 | /** 15 | * This is simmilar to one of 04Crosswisetests, but this time block size is set 16 | * to something greater than 0. BS is set to 1, which means that for every consecutive 17 | * frame there is one flow frame with continue to send status 18 | */ 19 | TEST_CASE ("BlockSize test", "[address]") 20 | { 21 | std::vector framesFromR; 22 | std::vector framesFromT; 23 | 24 | int called = 0; 25 | 26 | auto tpR = create ( 27 | Address (0x12, 0x89), 28 | [&called] (auto const &isoMessage) { 29 | ++called; 30 | REQUIRE (isoMessage.size () == 4095); 31 | }, 32 | [&framesFromR] (auto const &canFrame) { 33 | framesFromR.push_back (canFrame); 34 | return true; 35 | }); 36 | 37 | auto tpT = create ( 38 | Address (0x89, 0x12), 39 | [&called] (auto const &isoMessage) { 40 | ++called; 41 | REQUIRE (isoMessage.size () == 4095); 42 | }, 43 | 44 | [&framesFromT] (auto const &canFrame) { 45 | framesFromT.push_back (canFrame); 46 | return true; 47 | }); 48 | 49 | tpT.setBlockSize (1); 50 | tpT.send (std::vector (4095)); 51 | tpR.setBlockSize (1); 52 | tpR.send (std::vector (4095)); 53 | 54 | while (tpT.isSending () || tpR.isSending ()) { 55 | tpT.run (); 56 | for (CanFrame &f : framesFromT) { 57 | tpR.onCanNewFrame (f); 58 | } 59 | framesFromT.clear (); 60 | 61 | tpR.run (); 62 | for (CanFrame &f : framesFromR) { 63 | tpT.onCanNewFrame (f); 64 | } 65 | framesFromR.clear (); 66 | } 67 | 68 | REQUIRE (called == 2); 69 | } 70 | 71 | TEST_CASE ("SeparationTime test", "[address]") 72 | { 73 | std::vector framesFromR; 74 | std::vector framesFromT; 75 | 76 | int called = 0; 77 | 78 | auto tpR = create ( 79 | Address (0x12, 0x89), 80 | [&called] (auto const &isoMessage) { 81 | ++called; 82 | REQUIRE (isoMessage.size () == 4095); 83 | }, 84 | [&framesFromR] (auto const &canFrame) { 85 | framesFromR.push_back (canFrame); 86 | return true; 87 | }); 88 | 89 | auto tpT = create ( 90 | Address (0x89, 0x12), 91 | [&called] (auto const &isoMessage) { 92 | ++called; 93 | REQUIRE (isoMessage.size () == 4095); 94 | }, 95 | 96 | [&framesFromT] (auto const &canFrame) { 97 | framesFromT.push_back (canFrame); 98 | return true; 99 | }); 100 | 101 | tpT.setSeparationTime (0x3); 102 | tpT.send (std::vector (4095)); 103 | tpR.setSeparationTime (0x3); 104 | tpR.send (std::vector (4095)); 105 | 106 | while (tpT.isSending () || tpR.isSending ()) { 107 | tpT.run (); 108 | for (CanFrame &f : framesFromT) { 109 | tpR.onCanNewFrame (f); 110 | } 111 | framesFromT.clear (); 112 | 113 | tpR.run (); 114 | for (CanFrame &f : framesFromR) { 115 | tpT.onCanNewFrame (f); 116 | } 117 | framesFromR.clear (); 118 | } 119 | 120 | REQUIRE (called == 2); 121 | } 122 | -------------------------------------------------------------------------------- /test/socket-test/main.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | /****************************************************************************/ 26 | 27 | int createSocket () 28 | { 29 | int socketFd = socket (PF_CAN, SOCK_RAW, CAN_RAW); 30 | 31 | if (socketFd < 0) { 32 | fmt::print ("Error creating the socket\n"); 33 | return -1; 34 | } 35 | 36 | sockaddr_can addr{}; 37 | addr.can_family = AF_CAN; 38 | // addr.can_ifindex = 0; // Any can interface. I assume there is only one. 39 | // 40 | 41 | ifreq ifr{}; 42 | strncpy (ifr.ifr_name, "slcan0", IFNAMSIZ - 1); 43 | ifr.ifr_name[IFNAMSIZ - 1] = '\0'; 44 | ifr.ifr_ifindex = if_nametoindex (ifr.ifr_name); 45 | 46 | if (!ifr.ifr_ifindex) { 47 | fmt::print ("Error during if_nametoindex\n"); 48 | return -1; 49 | } 50 | 51 | addr.can_ifindex = ifr.ifr_ifindex; 52 | 53 | // setsockopt (socketFd, SOL_CAN_RAW, CAN_RAW_FILTER, NULL, 0); 54 | 55 | if (bind (socketFd, (struct sockaddr *)&addr, sizeof (addr)) < 0) { 56 | fmt::print ("Error during bind\n"); 57 | return -1; 58 | } 59 | 60 | return socketFd; 61 | } 62 | 63 | /** 64 | * Starts listening on a CAN_FD socket. 65 | */ 66 | template void listenSocket (int socketFd, C callback) 67 | { 68 | /* these settings are static and can be held out of the hot path */ 69 | iovec iov{}; 70 | msghdr msg{}; 71 | 72 | can_frame frame{}; 73 | iov.iov_base = &frame; 74 | // msg.msg_name = &addr; 75 | msg.msg_iov = &iov; 76 | msg.msg_iovlen = 1; 77 | fd_set rdfs{}; 78 | 79 | while (true) { 80 | FD_ZERO (&rdfs); 81 | FD_SET (socketFd, &rdfs); 82 | 83 | int ret{}; 84 | struct timeval timeout_config = {5, 0}; 85 | 86 | if ((ret = select (socketFd + 1, &rdfs, nullptr, nullptr, &timeout_config)) <= 0) { 87 | // running = false; 88 | continue; 89 | } 90 | 91 | if (FD_ISSET (socketFd, &rdfs)) { 92 | 93 | /* these settings may be modified by recvmsg() */ 94 | iov.iov_len = sizeof (frame); 95 | // msg.msg_namelen = sizeof (addr); 96 | msg.msg_flags = 0; 97 | 98 | int nbytes = recvmsg (socketFd, &msg, 0); 99 | 100 | if (nbytes < 0) { 101 | if (errno == ENETDOWN) { 102 | fmt::print ("Interface down\n"); 103 | continue; 104 | } 105 | 106 | fmt::print ("Error during read\n"); 107 | return; 108 | } 109 | 110 | callback (frame); 111 | } 112 | } 113 | } 114 | 115 | /****************************************************************************/ 116 | 117 | int sendSocket (int socketFd, can_frame const &frame) 118 | { 119 | int bytesToSend = int (sizeof (frame)); // CAN_MTU 120 | int bytesActuallySent = ::write (socketFd, &frame, bytesToSend); 121 | return (bytesActuallySent == bytesToSend); 122 | } 123 | 124 | /****************************************************************************/ 125 | 126 | int main () 127 | { 128 | using namespace tp; 129 | 130 | int socketFd = createSocket (); 131 | 132 | auto tp = create ( 133 | // Address{0x456, 0x123}, // Normal11 134 | // Address{0x789ABC, 0x123456}, // Normal29 135 | // Address{0x00, 0x00, 0x22, 0x11}, // NormalFixed29 136 | // Address{0x456, 0x123, 0xaa, 0x55}, // Extended11 137 | Address{0x789ABC, 0x123456, 0xaa, 0x55}, // Extended29 138 | // Address{0x00, 0x00, 0x22, 0x11, 0x99}, // Mixed29 139 | // Address{0x456, 0x123, 0x00, 0x00, 0x99}, // Mixed11 140 | [] (auto const &tm) { 141 | std::transform (std::begin (tm), std::end (tm), std::ostream_iterator (std::cout), [] (auto b) { 142 | static_assert (std::is_same::value); 143 | return char (b); 144 | }); 145 | 146 | std::cout << std::endl; 147 | }, 148 | [socketFd] (auto const &frame) { 149 | if (!sendSocket (socketFd, frame)) { 150 | fmt::print ("Error transmitting frame Id : {:x}, dlc : {}, data[0] = {}\n", frame.can_id, frame.can_dlc, 151 | frame.data[0]); 152 | 153 | return false; 154 | } 155 | 156 | return true; 157 | }, 158 | ChronoTimeProvider{}, [] (auto const &error) { std::cout << "Erorr : " << uint32_t (error) << std::endl; }); 159 | 160 | listenSocket (socketFd, [&tp] (auto const &frame) { 161 | // fmt::print ("Received frame Id : {:x}, dlc : {}, data[0] = {}\n", frame.can_id, frame.can_dlc, frame.data[0]); 162 | tp.onCanNewFrame (frame); 163 | }); 164 | } 165 | 166 | void receive () 167 | { 168 | using namespace tp; 169 | int socketFd = createSocket (); 170 | 171 | auto tp = create ( 172 | Address{0x789ABC, 0x123456}, [] (auto const &iso) { fmt::print ("Message size : {}\n", iso.size ()); }, 173 | [socketFd] (auto const &frame) { 174 | if (!sendSocket (socketFd, frame)) { 175 | fmt::print ("Error\n"); 176 | return false; 177 | } 178 | 179 | return true; 180 | }); 181 | 182 | listenSocket (socketFd, [&tp] (auto const &frame) { tp.onCanNewFrame (frame); }); 183 | } 184 | 185 | class FullCallback { 186 | public: 187 | void indication (tp::Address const &address, std::vector const &isoMessage, tp::Result result) {} 188 | void confirm (tp::Address const &address, tp::Result result) {} 189 | void firstFrameIndication (tp::Address const &address, uint16_t len) {} 190 | }; 191 | 192 | bool socketSend (can_frame const &frame) { return true; } 193 | 194 | void receive2 () 195 | { 196 | // using namespace tp; 197 | int socketFd = createSocket (); 198 | 199 | auto tp = tp::create (tp::Address{0x789ABC, 0x123456}, FullCallback (), socketSend); 200 | 201 | listenSocket (socketFd, [&tp] (auto const &frame) { tp.onCanNewFrame (frame); }); 202 | } 203 | -------------------------------------------------------------------------------- /test/unit-test/07IsoMessageTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | #include 12 | 13 | using namespace tp; 14 | 15 | /** 16 | * 17 | */ 18 | TEST_CASE ("Size constrained transmitter 16", "[isoMessage]") 19 | { 20 | std::vector framesFromR; 21 | std::vector framesFromT; 22 | 23 | bool called = false; 24 | auto tpR = create ( 25 | Address (0x89, 0x12), [&called] (auto const & /* isoMessage */) { called = true; }, 26 | [&framesFromR] (auto const &canFrame) { 27 | framesFromR.push_back (canFrame); 28 | return true; 29 | }); 30 | 31 | auto tpT = create ( 32 | Address (0x12, 0x89), [] (auto const & /*unused*/) {}, 33 | [&framesFromT] (auto const &canFrame) { 34 | framesFromT.push_back (canFrame); 35 | return true; 36 | }); 37 | 38 | REQUIRE (tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})); 39 | 40 | while (tpT.isSending ()) { 41 | tpT.run (); 42 | for (CanFrame &f : framesFromT) { 43 | tpR.onCanNewFrame (f); 44 | } 45 | framesFromT.clear (); 46 | 47 | tpR.run (); 48 | for (CanFrame &f : framesFromR) { 49 | tpT.onCanNewFrame (f); 50 | } 51 | framesFromR.clear (); 52 | } 53 | 54 | REQUIRE (called); 55 | } 56 | 57 | TEST_CASE ("Size constrained transmitter 15", "[isoMessage]") 58 | { 59 | std::vector framesFromR; 60 | std::vector framesFromT; 61 | 62 | bool called = false; 63 | auto tpR = create ( 64 | Address (0x89, 0x12), [&called] (auto const & /* isoMessage */) { called = true; }, 65 | [&framesFromR] (auto const &canFrame) { 66 | framesFromR.push_back (canFrame); 67 | return true; 68 | }); 69 | 70 | auto tpT = create ( 71 | Address (0x12, 0x89), [] (auto const & /*unused*/) {}, 72 | [&framesFromT] (auto const &canFrame) { 73 | framesFromT.push_back (canFrame); 74 | return true; 75 | }); 76 | 77 | REQUIRE (!tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})); 78 | 79 | while (tpT.isSending ()) { 80 | tpT.run (); 81 | for (CanFrame &f : framesFromT) { 82 | tpR.onCanNewFrame (f); 83 | } 84 | framesFromT.clear (); 85 | 86 | tpR.run (); 87 | for (CanFrame &f : framesFromR) { 88 | tpT.onCanNewFrame (f); 89 | } 90 | framesFromR.clear (); 91 | } 92 | 93 | REQUIRE (!called); 94 | } 95 | 96 | TEST_CASE ("Size constrained receiver 16", "[isoMessage]") 97 | { 98 | std::vector framesFromR; 99 | std::vector framesFromT; 100 | 101 | bool called = false; 102 | auto tpR = create ( 103 | Address (0x89, 0x12), [&called] (auto const & /* isoMessage */) { called = true; }, 104 | [&framesFromR] (auto const &canFrame) { 105 | framesFromR.push_back (canFrame); 106 | return true; 107 | }); 108 | 109 | auto tpT = create ( 110 | Address (0x12, 0x89), [] (auto const & /*unused*/) {}, 111 | [&framesFromT] (auto const &canFrame) { 112 | framesFromT.push_back (canFrame); 113 | return true; 114 | }); 115 | 116 | REQUIRE (tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})); 117 | 118 | while (tpT.isSending ()) { 119 | tpT.run (); 120 | for (CanFrame &f : framesFromT) { 121 | tpR.onCanNewFrame (f); 122 | } 123 | framesFromT.clear (); 124 | 125 | tpR.run (); 126 | for (CanFrame &f : framesFromR) { 127 | tpT.onCanNewFrame (f); 128 | } 129 | framesFromR.clear (); 130 | } 131 | 132 | REQUIRE (called); 133 | } 134 | 135 | TEST_CASE ("Size constrained receiver 15", "[isoMessage]") 136 | { 137 | std::vector framesFromR; 138 | std::vector framesFromT; 139 | 140 | bool called = false; 141 | auto tpR = create ( 142 | Address (0x89, 0x12), [&called] (auto const & /* isoMessage */) { called = true; }, 143 | [&framesFromR] (auto const &canFrame) { 144 | framesFromR.push_back (canFrame); 145 | return true; 146 | }); 147 | 148 | auto tpT = create ( 149 | Address (0x12, 0x89), [] (auto const & /*unused*/) {}, 150 | [&framesFromT] (auto const &canFrame) { 151 | framesFromT.push_back (canFrame); 152 | return true; 153 | }); 154 | 155 | REQUIRE (tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15})); 156 | 157 | while (tpT.isSending ()) { 158 | tpT.run (); 159 | for (CanFrame &f : framesFromT) { 160 | tpR.onCanNewFrame (f); 161 | } 162 | framesFromT.clear (); 163 | 164 | tpR.run (); 165 | for (CanFrame &f : framesFromR) { 166 | tpT.onCanNewFrame (f); 167 | } 168 | framesFromR.clear (); 169 | } 170 | 171 | REQUIRE (!called); 172 | REQUIRE (tpT.transportMessagesMap.empty ()); 173 | } 174 | 175 | TEST_CASE ("etl::vector 16B", "[isoMessage]") 176 | { 177 | std::vector framesFromR; 178 | std::vector framesFromT; 179 | 180 | using EtlVec = etl::vector; 181 | int called = 0; 182 | 183 | auto tpR = create ( 184 | Address (0x89, 0x12), 185 | [&called] (auto const &isoMessage) { 186 | ++called; 187 | REQUIRE (isoMessage.size () == 16); 188 | }, 189 | [&framesFromR] (auto const &canFrame) { 190 | framesFromR.push_back (canFrame); 191 | return true; 192 | }); 193 | 194 | auto tpT = create ( 195 | Address (0x12, 0x89), 196 | [&called] (auto const &isoMessage) { 197 | ++called; 198 | REQUIRE (isoMessage.size () == 16); 199 | }, 200 | [&framesFromT] (auto const &canFrame) { 201 | framesFromT.push_back (canFrame); 202 | return true; 203 | }); 204 | 205 | EtlVec v (16); 206 | REQUIRE (tpT.send (std::ref (v))); 207 | 208 | while (tpT.isSending ()) { 209 | tpT.run (); 210 | for (CanFrame &f : framesFromT) { 211 | tpR.onCanNewFrame (f); 212 | } 213 | framesFromT.clear (); 214 | 215 | tpR.run (); 216 | for (CanFrame &f : framesFromR) { 217 | tpT.onCanNewFrame (f); 218 | } 219 | framesFromR.clear (); 220 | } 221 | 222 | REQUIRE (called); 223 | } 224 | -------------------------------------------------------------------------------- /test/unit-test/08CallbackTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | #include 12 | 13 | using namespace tp; 14 | 15 | /** 16 | * Tests N_USData.indication 17 | */ 18 | TEST_CASE ("Advanced callback", "[callbacks]") 19 | { 20 | bool called = false; 21 | 22 | auto tpR = create ( 23 | Address (0x89, 0x67), 24 | [&called] (auto const &address, auto const &isoMessage, tp::Result result) { 25 | called = true; 26 | REQUIRE (isoMessage.size () == 1); 27 | REQUIRE (isoMessage[0] == 0x55); 28 | REQUIRE (result == Result::N_OK); 29 | REQUIRE (address.getTxId () == 0x89); 30 | }, 31 | [] (auto const & /* canFrame */) { return true; }); 32 | 33 | auto tpT = create ( 34 | Address (0x67, 0x89), [] (auto const & /*unused*/) {}, 35 | [&tpR] (auto const &canFrame) { 36 | tpR.onCanNewFrame (canFrame); 37 | return true; 38 | }); 39 | 40 | tpT.send ({0x55}); 41 | 42 | while (tpT.isSending ()) { 43 | tpT.run (); 44 | } 45 | 46 | REQUIRE (called); 47 | } 48 | 49 | TEST_CASE ("AdvancedMethod callback", "[callbacks]") 50 | { 51 | bool called = false; 52 | 53 | class FullCallback { 54 | public: 55 | explicit FullCallback (bool &b) : called{b} {} 56 | 57 | void confirm (Address const &a, Result r) {} 58 | // void firstFrameIndication (Address const &a, uint16_t len) {} 59 | void indication (Address const &address, std::vector const &isoMessage, Result result) 60 | { 61 | called = true; 62 | REQUIRE (isoMessage.size () == 1); 63 | REQUIRE (isoMessage[0] == 0x55); 64 | REQUIRE (result == Result::N_OK); 65 | REQUIRE (address.getTxId () == 0x89); 66 | } 67 | 68 | private: 69 | bool &called; 70 | }; 71 | 72 | auto tpR = create (Address (0x89, 0x67), FullCallback (called), [] (auto const & /* canFrame */) { return true; }); 73 | 74 | auto tpT = create ( 75 | Address (0x67, 0x89), [] (auto const & /*unused*/) {}, 76 | [&tpR] (auto const &canFrame) { 77 | tpR.onCanNewFrame (canFrame); 78 | return true; 79 | }); 80 | 81 | tpT.send ({0x55}); 82 | 83 | while (tpT.isSending ()) { 84 | tpT.run (); 85 | } 86 | 87 | REQUIRE (called); 88 | } 89 | 90 | /** 91 | * Test M_USData.confirm (5.2.2) 92 | */ 93 | TEST_CASE ("confirm callback", "[callbacks]") 94 | { 95 | /* 96 | * Successful transmission. 97 | */ 98 | { 99 | int called = 0; 100 | 101 | class FullCallbackR { 102 | public: 103 | explicit FullCallbackR (int &b) : called{b} {} 104 | 105 | void indication (Address const &address, std::vector const &isoMessage, Result result) 106 | { 107 | ++called; 108 | REQUIRE (isoMessage.size () == 1); 109 | REQUIRE (isoMessage[0] == 0x55); 110 | REQUIRE (result == Result::N_OK); 111 | REQUIRE (address.getTxId () == 0x89); 112 | } 113 | 114 | private: 115 | int &called; 116 | }; 117 | 118 | auto tpR = create (Address (0x89, 0x67), FullCallbackR (called), [] (auto const & /* canFrame */) { return true; }); 119 | 120 | class FullCallbackT { 121 | public: 122 | explicit FullCallbackT (int &b) : called{b} {} 123 | 124 | void confirm (Address const &address, Result result) 125 | { 126 | ++called; 127 | REQUIRE (address.getTxId () == 0x89); 128 | REQUIRE (result == Result::N_OK); 129 | } 130 | 131 | void indication (Address const & /* address */, std::vector const & /* isoMessage */, Result /* result */) {} 132 | 133 | private: 134 | int &called; 135 | }; 136 | 137 | auto tpT = create (Address (0x67, 0x89), FullCallbackT (called), [&tpR] (auto const &canFrame) { 138 | tpR.onCanNewFrame (canFrame); 139 | return true; 140 | }); 141 | 142 | tpT.send ({0x55}); 143 | 144 | while (tpT.isSending ()) { 145 | tpT.run (); 146 | } 147 | 148 | REQUIRE (called == 2); 149 | } 150 | 151 | /* 152 | * UNsuccessful transmission. 153 | */ 154 | { 155 | int called = 0; 156 | 157 | class FullCallback { 158 | public: 159 | explicit FullCallback (int &b) : called{b} {} 160 | 161 | void confirm (Address const &address, Result result) 162 | { 163 | ++called; 164 | REQUIRE (address.getTxId () == 0x89); 165 | REQUIRE (result != Result::N_OK); 166 | } 167 | 168 | void indication (Address const & /* address */, std::vector const & /* isoMessage */, Result /* result */) 169 | { 170 | ++called; 171 | } 172 | 173 | private: 174 | int &called; 175 | }; 176 | 177 | auto tpR = create ( 178 | Address (0x89, 0x67), [] (auto /*iso*/) {}, [] (auto const & /* canFrame */) { return true; }); 179 | 180 | auto tpT = create (Address (0x67, 0x89), FullCallback (called), [] (auto const & /* canFrame */) { 181 | // An error occureed during CAN frame sending. 182 | return false; 183 | }); 184 | 185 | tpT.send ({0x55}); 186 | 187 | while (tpT.isSending ()) { 188 | tpT.run (); 189 | } 190 | 191 | REQUIRE (called == 1); 192 | REQUIRE (tpT.transportMessagesMap.empty ()); 193 | } 194 | } 195 | 196 | TEST_CASE ("First Frame callback", "[callbacks]") 197 | { 198 | std::vector framesFromR; 199 | std::vector framesFromT; 200 | 201 | int called = 0; 202 | 203 | class FullCallbackR { 204 | public: 205 | explicit FullCallbackR (int &b) : called{b} {} 206 | 207 | void indication (Address const &address, std::vector const &isoMessage, Result result) 208 | { 209 | ++called; 210 | REQUIRE (isoMessage.size () == 11); 211 | REQUIRE (isoMessage[0] == 1); 212 | REQUIRE (result == Result::N_OK); 213 | REQUIRE (address.getTxId () == 0x89); 214 | } 215 | 216 | void firstFrameIndication (Address const &address, uint16_t len) 217 | { 218 | REQUIRE (address.getTxId () == 0x89); 219 | called += len; 220 | } 221 | 222 | private: 223 | int &called; 224 | }; 225 | 226 | auto tpR = create (Address (0x89, 0x67), FullCallbackR (called), [&framesFromR] (auto const &canFrame) { 227 | framesFromR.push_back (canFrame); 228 | return true; 229 | }); 230 | 231 | auto tpT = create ( 232 | Address (0x67, 0x89), [] (auto /* a */) {}, 233 | [&framesFromT] (auto const &canFrame) { 234 | framesFromT.push_back (canFrame); 235 | return true; 236 | }); 237 | 238 | // Has to be long enough to be split between first and consecutive frames 239 | tpT.send ({1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}); 240 | 241 | while (tpT.isSending ()) { 242 | tpT.run (); 243 | for (CanFrame &f : framesFromT) { 244 | tpR.onCanNewFrame (f); 245 | } 246 | framesFromT.clear (); 247 | 248 | tpR.run (); 249 | for (CanFrame &f : framesFromR) { 250 | tpT.onCanNewFrame (f); 251 | } 252 | framesFromR.clear (); 253 | } 254 | 255 | REQUIRE (called == 12); 256 | } 257 | -------------------------------------------------------------------------------- /test/unit-test/04CrosswiseTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | #include 12 | 13 | using namespace tp; 14 | 15 | TEST_CASE ("cross half-duplex 1B", "[crosswise]") 16 | { 17 | bool called = false; 18 | auto tpR = create ( 19 | Address (0x89, 0x67), 20 | [&called] (auto const &isoMessage) { 21 | called = true; 22 | REQUIRE (isoMessage.size () == 1); 23 | REQUIRE (isoMessage[0] == 0x55); 24 | }, 25 | [] (auto const & /* canFrame */) { return true; }); 26 | 27 | auto tpT = create ( 28 | Address (0x67, 0x89), [] (auto const & /*unused*/) {}, 29 | [&tpR] (auto const &canFrame) { 30 | tpR.onCanNewFrame (canFrame); 31 | return true; 32 | }); 33 | 34 | tpT.send ({0x55}); 35 | 36 | while (tpT.isSending ()) { 37 | tpT.run (); 38 | } 39 | 40 | REQUIRE (called); 41 | } 42 | 43 | TEST_CASE ("cross half-duplex 16B", "[crosswise]") 44 | { 45 | std::vector framesFromR; 46 | std::vector framesFromT; 47 | 48 | bool called = false; 49 | auto tpR = create ( 50 | Address (0x89, 0x12), 51 | [&called] (auto const &isoMessage) { 52 | called = true; 53 | REQUIRE (isoMessage.size () == 16); 54 | REQUIRE (isoMessage[0] == 0); 55 | REQUIRE (isoMessage[1] == 1); 56 | REQUIRE (isoMessage[2] == 2); 57 | REQUIRE (isoMessage[3] == 3); 58 | REQUIRE (isoMessage[4] == 4); 59 | REQUIRE (isoMessage[5] == 5); 60 | REQUIRE (isoMessage[6] == 6); 61 | REQUIRE (isoMessage[7] == 7); 62 | REQUIRE (isoMessage[8] == 8); 63 | REQUIRE (isoMessage[9] == 9); 64 | REQUIRE (isoMessage[10] == 10); 65 | REQUIRE (isoMessage[11] == 11); 66 | REQUIRE (isoMessage[12] == 12); 67 | REQUIRE (isoMessage[13] == 13); 68 | REQUIRE (isoMessage[14] == 14); 69 | REQUIRE (isoMessage[15] == 15); 70 | }, 71 | [&framesFromR] (auto const &canFrame) { 72 | framesFromR.push_back (canFrame); 73 | return true; 74 | }); 75 | 76 | auto tpT = create ( 77 | Address (0x12, 0x89), [] (auto const & /*unused*/) {}, 78 | [&framesFromT] (auto const &canFrame) { 79 | framesFromT.push_back (canFrame); 80 | return true; 81 | }); 82 | 83 | tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); 84 | 85 | while (tpT.isSending ()) { 86 | tpT.run (); 87 | for (CanFrame &f : framesFromT) { 88 | tpR.onCanNewFrame (f); 89 | } 90 | framesFromT.clear (); 91 | 92 | tpR.run (); 93 | for (CanFrame &f : framesFromR) { 94 | tpT.onCanNewFrame (f); 95 | } 96 | framesFromR.clear (); 97 | } 98 | 99 | REQUIRE (called); 100 | } 101 | 102 | TEST_CASE ("cross half-duplex 16B wrong source", "[crosswise]") 103 | { 104 | std::vector framesFromR; 105 | std::vector framesFromT; 106 | 107 | bool called = false; 108 | // Target address is 0x12, source address is 0x89. 109 | auto tpR = create ( 110 | Address (0x12, 0x89), [&called] (auto const & /* isoMessage */) { called = true; }, 111 | [&framesFromR] (auto const &canFrame) { 112 | framesFromR.push_back (canFrame); 113 | return true; 114 | }); 115 | 116 | // Target address is 0x89, source address is WRONGLY set to 0x13! 117 | auto tpT = create ( 118 | Address (0x89, 0x13), [] (auto const &) {}, 119 | [&framesFromT] (auto const &canFrame) { 120 | framesFromT.push_back (canFrame); 121 | return true; 122 | }); 123 | 124 | tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); 125 | 126 | while (tpT.isSending ()) { 127 | tpT.run (); 128 | for (CanFrame &f : framesFromT) { 129 | tpR.onCanNewFrame (f); 130 | } 131 | framesFromT.clear (); 132 | 133 | tpR.run (); 134 | for (CanFrame &f : framesFromR) { 135 | tpT.onCanNewFrame (f); 136 | } 137 | framesFromR.clear (); 138 | } 139 | 140 | // Not called because of wrong source address 141 | REQUIRE (!called); 142 | } 143 | 144 | TEST_CASE ("cross full-duplex 16B", "[crosswise]") 145 | { 146 | std::vector framesFromR; 147 | std::vector framesFromT; 148 | 149 | int called = 0; 150 | 151 | // Target address is 0x12, source address is 0x89. 152 | auto tpR = create ( 153 | Address (0x12, 0x89), 154 | [&called] (auto const &isoMessage) { 155 | ++called; 156 | REQUIRE (isoMessage.size () == 16); 157 | REQUIRE (isoMessage[0] == 0); 158 | REQUIRE (isoMessage[1] == 1); 159 | REQUIRE (isoMessage[2] == 2); 160 | REQUIRE (isoMessage[3] == 3); 161 | REQUIRE (isoMessage[4] == 4); 162 | REQUIRE (isoMessage[5] == 5); 163 | REQUIRE (isoMessage[6] == 6); 164 | REQUIRE (isoMessage[7] == 7); 165 | REQUIRE (isoMessage[8] == 8); 166 | REQUIRE (isoMessage[9] == 9); 167 | REQUIRE (isoMessage[10] == 10); 168 | REQUIRE (isoMessage[11] == 11); 169 | REQUIRE (isoMessage[12] == 12); 170 | REQUIRE (isoMessage[13] == 13); 171 | REQUIRE (isoMessage[14] == 14); 172 | REQUIRE (isoMessage[15] == 15); 173 | }, 174 | [&framesFromR] (auto const &canFrame) { 175 | framesFromR.push_back (canFrame); 176 | return true; 177 | }); 178 | 179 | // Target address is 0x89, source address is 0x12. 180 | auto tpT = create ( 181 | Address (0x89, 0x12), 182 | [&called] (auto const &isoMessage) { 183 | ++called; 184 | REQUIRE (isoMessage.size () == 16); 185 | REQUIRE (isoMessage[0] == 15); 186 | REQUIRE (isoMessage[1] == 14); 187 | REQUIRE (isoMessage[2] == 13); 188 | REQUIRE (isoMessage[3] == 12); 189 | REQUIRE (isoMessage[4] == 11); 190 | REQUIRE (isoMessage[5] == 10); 191 | REQUIRE (isoMessage[6] == 9); 192 | REQUIRE (isoMessage[7] == 8); 193 | REQUIRE (isoMessage[8] == 7); 194 | REQUIRE (isoMessage[9] == 6); 195 | REQUIRE (isoMessage[10] == 5); 196 | REQUIRE (isoMessage[11] == 4); 197 | REQUIRE (isoMessage[12] == 3); 198 | REQUIRE (isoMessage[13] == 2); 199 | REQUIRE (isoMessage[14] == 1); 200 | REQUIRE (isoMessage[15] == 0); 201 | }, 202 | 203 | [&framesFromT] (auto const &canFrame) { 204 | framesFromT.push_back (canFrame); 205 | return true; 206 | }); 207 | 208 | tpT.send ({0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}); 209 | tpR.send ({15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0}); 210 | 211 | while (tpT.isSending ()) { 212 | tpT.run (); 213 | for (CanFrame &f : framesFromT) { 214 | tpR.onCanNewFrame (f); 215 | } 216 | framesFromT.clear (); 217 | 218 | tpR.run (); 219 | for (CanFrame &f : framesFromR) { 220 | tpT.onCanNewFrame (f); 221 | } 222 | framesFromR.clear (); 223 | } 224 | 225 | REQUIRE (called == 2); 226 | } 227 | 228 | TEST_CASE ("cross full-duplex 4095B", "[crosswise]") 229 | { 230 | std::vector framesFromR; 231 | std::vector framesFromT; 232 | 233 | int called = 0; 234 | 235 | auto tpR = create ( 236 | Address (0x12, 0x89), 237 | [&called] (auto const &isoMessage) { 238 | ++called; 239 | REQUIRE (isoMessage.size () == 4095); 240 | }, 241 | [&framesFromR] (auto const &canFrame) { 242 | framesFromR.push_back (canFrame); 243 | return true; 244 | }); 245 | 246 | auto tpT = create ( 247 | Address (0x89, 0x12), 248 | [&called] (auto const &isoMessage) { 249 | ++called; 250 | REQUIRE (isoMessage.size () == 4095); 251 | }, 252 | 253 | [&framesFromT] (auto const &canFrame) { 254 | framesFromT.push_back (canFrame); 255 | return true; 256 | }); 257 | 258 | tpT.send (std::vector (4095)); 259 | tpR.send (std::vector (4095)); 260 | 261 | while (tpT.isSending () || tpR.isSending ()) { 262 | tpT.run (); 263 | for (CanFrame &f : framesFromT) { 264 | tpR.onCanNewFrame (f); 265 | } 266 | framesFromT.clear (); 267 | 268 | tpR.run (); 269 | for (CanFrame &f : framesFromR) { 270 | tpT.onCanNewFrame (f); 271 | } 272 | framesFromR.clear (); 273 | } 274 | 275 | REQUIRE (called == 2); 276 | } 277 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,bugprone-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,readability-*,-modernize-use-trailing-return-type,-modernize-use-nodiscard' 3 | WarningsAsErrors: '' 4 | HeaderFilterRegex: '' 5 | AnalyzeTemporaryDtors: false 6 | FormatStyle: none 7 | User: iwasz 8 | CheckOptions: 9 | - key: bugprone-argument-comment.CommentBoolLiterals 10 | value: '0' 11 | - key: bugprone-argument-comment.CommentCharacterLiterals 12 | value: '0' 13 | - key: bugprone-argument-comment.CommentFloatLiterals 14 | value: '0' 15 | - key: bugprone-argument-comment.CommentIntegerLiterals 16 | value: '0' 17 | - key: bugprone-argument-comment.CommentNullPtrs 18 | value: '0' 19 | - key: bugprone-argument-comment.CommentStringLiterals 20 | value: '0' 21 | - key: bugprone-argument-comment.CommentUserDefinedLiterals 22 | value: '0' 23 | - key: bugprone-argument-comment.StrictMode 24 | value: '0' 25 | - key: bugprone-assert-side-effect.AssertMacros 26 | value: assert 27 | - key: bugprone-assert-side-effect.CheckFunctionCalls 28 | value: '0' 29 | - key: bugprone-dangling-handle.HandleClasses 30 | value: 'std::basic_string_view;std::experimental::basic_string_view' 31 | - key: bugprone-exception-escape.FunctionsThatShouldNotThrow 32 | value: '' 33 | - key: bugprone-exception-escape.IgnoredExceptions 34 | value: '' 35 | - key: bugprone-misplaced-widening-cast.CheckImplicitCasts 36 | value: '0' 37 | - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant 38 | value: '1' 39 | - key: bugprone-sizeof-expression.WarnOnSizeOfConstant 40 | value: '1' 41 | - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression 42 | value: '0' 43 | - key: bugprone-sizeof-expression.WarnOnSizeOfThis 44 | value: '1' 45 | - key: bugprone-string-constructor.LargeLengthThreshold 46 | value: '8388608' 47 | - key: bugprone-string-constructor.WarnOnLargeLength 48 | value: '1' 49 | - key: bugprone-suspicious-enum-usage.StrictMode 50 | value: '0' 51 | - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens 52 | value: '5' 53 | - key: bugprone-suspicious-missing-comma.RatioThreshold 54 | value: '0.200000' 55 | - key: bugprone-suspicious-missing-comma.SizeThreshold 56 | value: '5' 57 | - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions 58 | value: '' 59 | - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison 60 | value: '1' 61 | - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison 62 | value: '0' 63 | - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit 64 | value: '16' 65 | - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField 66 | value: '1' 67 | - key: bugprone-unused-return-value.CheckedFunctions 68 | value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty' 69 | - key: cert-dcl16-c.NewSuffixes 70 | value: 'L;LL;LU;LLU' 71 | - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField 72 | value: '0' 73 | - key: cppcoreguidelines-avoid-magic-numbers.IgnoredFloatingPointValues 74 | value: '1.0;100.0;' 75 | - key: cppcoreguidelines-avoid-magic-numbers.IgnoredIntegerValues 76 | value: '1;2;3;4;' 77 | - key: cppcoreguidelines-explicit-virtual-functions.FinalSpelling 78 | value: final 79 | - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors 80 | value: '1' 81 | - key: cppcoreguidelines-explicit-virtual-functions.OverrideSpelling 82 | value: override 83 | - key: cppcoreguidelines-macro-usage.AllowedRegexp 84 | value: '^DEBUG_*' 85 | - key: cppcoreguidelines-macro-usage.CheckCapsOnly 86 | value: '0' 87 | - key: cppcoreguidelines-macro-usage.IgnoreCommandLineMacros 88 | value: '1' 89 | - key: cppcoreguidelines-no-malloc.Allocations 90 | value: '::malloc;::calloc' 91 | - key: cppcoreguidelines-no-malloc.Deallocations 92 | value: '::free' 93 | - key: cppcoreguidelines-no-malloc.Reallocations 94 | value: '::realloc' 95 | - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 96 | value: '1' 97 | - key: cppcoreguidelines-owning-memory.LegacyResourceConsumers 98 | value: '::free;::realloc;::freopen;::fclose' 99 | - key: cppcoreguidelines-owning-memory.LegacyResourceProducers 100 | value: '::malloc;::aligned_alloc;::realloc;::calloc;::fopen;::freopen;::tmpfile' 101 | - key: cppcoreguidelines-pro-bounds-constant-array-index.GslHeader 102 | value: '' 103 | - key: cppcoreguidelines-pro-bounds-constant-array-index.IncludeStyle 104 | value: '0' 105 | - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays 106 | value: '0' 107 | - key: cppcoreguidelines-pro-type-member-init.UseAssignment 108 | value: '0' 109 | - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions 110 | value: '0' 111 | - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor 112 | value: '0' 113 | - key: google-readability-braces-around-statements.ShortStatementLines 114 | value: '1' 115 | - key: google-readability-function-size.StatementThreshold 116 | value: '800' 117 | - key: google-readability-namespace-comments.ShortNamespaceLines 118 | value: '10' 119 | - key: google-readability-namespace-comments.SpacesBeforeComments 120 | value: '2' 121 | - key: misc-definitions-in-headers.HeaderFileExtensions 122 | value: ',h,hh,hpp,hxx' 123 | - key: misc-definitions-in-headers.UseHeaderFileExtension 124 | value: '1' 125 | - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries 126 | value: '1' 127 | - key: misc-unused-parameters.StrictMode 128 | value: '0' 129 | - key: modernize-loop-convert.MaxCopySize 130 | value: '16' 131 | - key: modernize-loop-convert.MinConfidence 132 | value: reasonable 133 | - key: modernize-loop-convert.NamingStyle 134 | value: CamelCase 135 | - key: modernize-make-shared.IgnoreMacros 136 | value: '1' 137 | - key: modernize-make-shared.IncludeStyle 138 | value: '0' 139 | - key: modernize-make-shared.MakeSmartPtrFunction 140 | value: 'std::make_shared' 141 | - key: modernize-make-shared.MakeSmartPtrFunctionHeader 142 | value: memory 143 | - key: modernize-make-unique.IgnoreMacros 144 | value: '1' 145 | - key: modernize-make-unique.IncludeStyle 146 | value: '0' 147 | - key: modernize-make-unique.MakeSmartPtrFunction 148 | value: 'std::make_unique' 149 | - key: modernize-make-unique.MakeSmartPtrFunctionHeader 150 | value: memory 151 | - key: modernize-pass-by-value.IncludeStyle 152 | value: llvm 153 | - key: modernize-pass-by-value.ValuesOnly 154 | value: '0' 155 | - key: modernize-raw-string-literal.ReplaceShorterLiterals 156 | value: '0' 157 | - key: modernize-replace-auto-ptr.IncludeStyle 158 | value: llvm 159 | - key: modernize-replace-random-shuffle.IncludeStyle 160 | value: llvm 161 | - key: modernize-use-auto.MinTypeNameLength 162 | value: '5' 163 | - key: modernize-use-auto.RemoveStars 164 | value: '0' 165 | - key: modernize-use-default-member-init.IgnoreMacros 166 | value: '1' 167 | - key: modernize-use-default-member-init.UseAssignment 168 | value: '0' 169 | - key: modernize-use-emplace.ContainersWithPushBack 170 | value: '::std::vector;::std::list;::std::deque' 171 | - key: modernize-use-emplace.SmartPointers 172 | value: '::std::shared_ptr;::std::unique_ptr;::std::auto_ptr;::std::weak_ptr' 173 | - key: modernize-use-emplace.TupleMakeFunctions 174 | value: '::std::make_pair;::std::make_tuple' 175 | - key: modernize-use-emplace.TupleTypes 176 | value: '::std::pair;::std::tuple' 177 | - key: modernize-use-equals-default.IgnoreMacros 178 | value: '1' 179 | - key: modernize-use-equals-delete.IgnoreMacros 180 | value: '1' 181 | - key: modernize-use-nodiscard.ReplacementString 182 | value: '[[nodiscard]]' 183 | - key: modernize-use-noexcept.ReplacementString 184 | value: '' 185 | - key: modernize-use-noexcept.UseNoexceptFalse 186 | value: '1' 187 | - key: modernize-use-nullptr.NullMacros 188 | value: 'NULL' 189 | - key: modernize-use-override.FinalSpelling 190 | value: final 191 | - key: modernize-use-override.IgnoreDestructors 192 | value: '0' 193 | - key: modernize-use-override.OverrideSpelling 194 | value: override 195 | - key: modernize-use-transparent-functors.SafeMode 196 | value: '0' 197 | - key: modernize-use-using.IgnoreMacros 198 | value: '1' 199 | - key: performance-faster-string-find.StringLikeClasses 200 | value: 'std::basic_string' 201 | - key: performance-for-range-copy.AllowedTypes 202 | value: '' 203 | - key: performance-for-range-copy.WarnOnAllAutoCopies 204 | value: '0' 205 | - key: performance-inefficient-string-concatenation.StrictMode 206 | value: '0' 207 | - key: performance-inefficient-vector-operation.VectorLikeClasses 208 | value: '::std::vector' 209 | - key: performance-move-const-arg.CheckTriviallyCopyableMove 210 | value: '1' 211 | - key: performance-move-constructor-init.IncludeStyle 212 | value: llvm 213 | - key: performance-type-promotion-in-math-fn.IncludeStyle 214 | value: llvm 215 | - key: performance-unnecessary-copy-initialization.AllowedTypes 216 | value: '' 217 | - key: performance-unnecessary-value-param.AllowedTypes 218 | value: '' 219 | - key: performance-unnecessary-value-param.IncludeStyle 220 | value: llvm 221 | - key: readability-braces-around-statements.ShortStatementLines 222 | value: '0' 223 | - key: readability-function-size.BranchThreshold 224 | value: '4294967295' 225 | - key: readability-function-size.LineThreshold 226 | value: '4294967295' 227 | - key: readability-function-size.NestingThreshold 228 | value: '4294967295' 229 | - key: readability-function-size.ParameterThreshold 230 | value: '4294967295' 231 | - key: readability-function-size.StatementThreshold 232 | value: '800' 233 | - key: readability-function-size.VariableThreshold 234 | value: '4294967295' 235 | - key: readability-identifier-naming.IgnoreFailedSplit 236 | value: '0' 237 | - key: readability-implicit-bool-conversion.AllowIntegerConditions 238 | value: '0' 239 | - key: readability-implicit-bool-conversion.AllowPointerConditions 240 | value: '0' 241 | - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros 242 | value: '1' 243 | - key: readability-inconsistent-declaration-parameter-name.Strict 244 | value: '0' 245 | - key: readability-magic-numbers.IgnoredFloatingPointValues 246 | value: '1.0;100.0;' 247 | - key: readability-magic-numbers.IgnoredIntegerValues 248 | value: '1;2;3;4;' 249 | - key: readability-redundant-smartptr-get.IgnoreMacros 250 | value: '1' 251 | - key: readability-simplify-boolean-expr.ChainedConditionalAssignment 252 | value: '0' 253 | - key: readability-simplify-boolean-expr.ChainedConditionalReturn 254 | value: '0' 255 | - key: readability-simplify-subscript-expr.Types 256 | value: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' 257 | - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold 258 | value: '3' 259 | - key: readability-uppercase-literal-suffix.IgnoreMacros 260 | value: '1' 261 | - key: readability-uppercase-literal-suffix.NewSuffixes 262 | value: '' 263 | ... 264 | 265 | -------------------------------------------------------------------------------- /test/unit-test/02SendTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace tp; 15 | 16 | TEST_CASE ("tx 1B", "[send]") 17 | { 18 | bool called = false; 19 | auto tp = create ( 20 | {0, 0}, [] (auto const & /*unused*/) {}, 21 | [&called] (auto const &canFrame) { 22 | called = true; 23 | REQUIRE (canFrame.data[0] == 0x01); // SINGLE FRAME, 1B length 24 | REQUIRE (canFrame.data[1] == 0x55); // actual data 25 | REQUIRE (canFrame.id == 0x89); 26 | return true; 27 | }); 28 | 29 | tp.send (Address (0x67, 0x89), {0x55}); 30 | REQUIRE (called); 31 | } 32 | 33 | TEST_CASE ("tx 7B", "[send]") 34 | { 35 | bool called = false; 36 | auto tp = create ( 37 | {0, 0}, [] (auto const & /* unused*/) {}, 38 | [&called] (auto &&canFrame) { 39 | called = true; 40 | REQUIRE (canFrame.data[0] == 0x07); // SINGLE FRAME, 7B length 41 | REQUIRE (canFrame.data[1] == 0); // actual data 42 | REQUIRE (canFrame.data[2] == 1); 43 | REQUIRE (canFrame.data[3] == 2); 44 | REQUIRE (canFrame.data[4] == 3); 45 | REQUIRE (canFrame.data[5] == 4); 46 | REQUIRE (canFrame.data[6] == 5); 47 | REQUIRE (canFrame.data[7] == 6); 48 | return true; 49 | }); 50 | 51 | tp.send (Address (0x00, 0x00), {0, 1, 2, 3, 4, 5, 6}); 52 | REQUIRE (called); 53 | } 54 | 55 | /****************************************************************************/ 56 | 57 | TEST_CASE ("tx 8B", "[send]") 58 | { 59 | int calledTimes = 0; 60 | 61 | auto tp = create ( 62 | {0, 0}, [] (auto const & /*unused*/) {}, 63 | [&calledTimes] (auto const &canFrame) { 64 | // 1. It should send a first frame 65 | if (calledTimes == 0) { 66 | REQUIRE (int (canFrame.data[0]) == 0x10); // FIRST FRAME 67 | REQUIRE (int (canFrame.data[1]) == 8); // 8B length 68 | REQUIRE (int (canFrame.data[2]) == 0); // First byte 69 | REQUIRE (int (canFrame.data[3]) == 1); 70 | REQUIRE (int (canFrame.data[4]) == 2); 71 | REQUIRE (int (canFrame.data[5]) == 3); 72 | REQUIRE (int (canFrame.data[6]) == 4); 73 | REQUIRE (int (canFrame.data[7]) == 5); 74 | ++calledTimes; 75 | return true; 76 | } 77 | 78 | // 2. It should send a consecutive frame 79 | if (calledTimes == 1) { 80 | REQUIRE (int (canFrame.data[0]) == 0x21); // CONSECUTIVE FRAME, serial number 1 81 | REQUIRE (int (canFrame.data[1]) == 6); // actual data - 7th byte 82 | REQUIRE (int (canFrame.data[2]) == 7); // actual data - last, eight byte 83 | ++calledTimes; 84 | return true; 85 | } 86 | 87 | return false; 88 | }); 89 | 90 | tp.send (Address (0x00, 0x00), {0, 1, 2, 3, 4, 5, 6, 7}); 91 | tp.run (); // IDLE -> SEND_FIRST_FRAME 92 | tp.run (); // SEND_FIRST_FRAME -> RECEIVE_FLOW_FRAME 93 | tp.onCanNewFrame (CanFrame (0x00, true, 0x30)); // RECEIVE_FLOW_FRAME -> SEND_CONSECUTIVE_FRAME 94 | tp.run (); // SEND_CONSECUTIVE_FRAME -> DONE 95 | 96 | while (tp.isSending ()) { 97 | tp.run (); 98 | } 99 | 100 | REQUIRE (calledTimes == 2); 101 | } 102 | 103 | /****************************************************************************/ 104 | 105 | TEST_CASE ("tx 12B", "[send]") 106 | { 107 | int calledTimes = 0; 108 | auto tp = create ( 109 | {0, 0}, [] (auto const & /* unused*/) {}, 110 | [&calledTimes] (auto &&canFrame) { 111 | // 1. It should send a first frame 112 | if (calledTimes == 0) { 113 | REQUIRE (int (canFrame.data[0]) == 0x10); // FIRST FRAME 114 | REQUIRE (int (canFrame.data[1]) == 13); // 13B length 115 | REQUIRE (int (canFrame.data[2]) == 0); // First byte 116 | REQUIRE (int (canFrame.data[3]) == 1); 117 | REQUIRE (int (canFrame.data[4]) == 2); 118 | REQUIRE (int (canFrame.data[5]) == 3); 119 | REQUIRE (int (canFrame.data[6]) == 4); 120 | REQUIRE (int (canFrame.data[7]) == 5); 121 | ++calledTimes; 122 | return true; 123 | } 124 | 125 | // 2. It should send a consecutive frame 126 | if (calledTimes == 1) { 127 | REQUIRE (int (canFrame.data[0]) == 0x21); // CONSECUTIVE FRAME, serial number 1 128 | REQUIRE (int (canFrame.data[1]) == 6); 129 | REQUIRE (int (canFrame.data[2]) == 7); 130 | REQUIRE (int (canFrame.data[3]) == 8); 131 | REQUIRE (int (canFrame.data[4]) == 9); 132 | REQUIRE (int (canFrame.data[5]) == 10); 133 | REQUIRE (int (canFrame.data[6]) == 11); 134 | REQUIRE (int (canFrame.data[7]) == 12); 135 | ++calledTimes; 136 | return true; 137 | } 138 | 139 | return false; 140 | }); 141 | 142 | tp.send (Address (0x00, 0x00), {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}); 143 | tp.run (); // IDLE -> SEND_FIRST_FRAME 144 | tp.run (); // SEND_FIRST_FRAME -> RECEIVE_FLOW_FRAME 145 | tp.onCanNewFrame (CanFrame (0x00, true, 0x30)); // RECEIVE_FLOW_FRAME -> SEND_CONSECUTIVE_FRAME 146 | tp.run (); // SEND_CONSECUTIVE_FRAME -> DONE 147 | 148 | while (tp.isSending ()) { 149 | tp.run (); 150 | } 151 | 152 | REQUIRE (calledTimes == 2); 153 | } 154 | 155 | TEST_CASE ("tx 4095B", "[send]") 156 | { 157 | int calledTimes = 0; 158 | auto tp = create ( 159 | {0, 0}, [] (auto const & /*unused*/) {}, 160 | [&calledTimes] (auto &&canFrame) { 161 | if (calledTimes == 0) { 162 | REQUIRE (int (canFrame.dlc) == 8); 163 | REQUIRE (int (canFrame.data[0]) == 0x1f); // FIRST FRAME 164 | REQUIRE (int (canFrame.data[1]) == 0xff); // 13B length 165 | REQUIRE (int (canFrame.data[2]) == 0); // First byte 166 | REQUIRE (int (canFrame.data[3]) == 1); 167 | REQUIRE (int (canFrame.data[4]) == 2); 168 | REQUIRE (int (canFrame.data[5]) == 3); 169 | REQUIRE (int (canFrame.data[6]) == 4); 170 | REQUIRE (int (canFrame.data[7]) == 5); 171 | } 172 | 173 | if (calledTimes == 1) { 174 | REQUIRE (int (canFrame.dlc) == 8); 175 | REQUIRE (int (canFrame.data[0]) == 0x21); // CONSECUTIVE FRAME, serial number 1 176 | REQUIRE (int (canFrame.data[1]) == 6); 177 | REQUIRE (int (canFrame.data[2]) == 7); 178 | REQUIRE (int (canFrame.data[3]) == 8); 179 | REQUIRE (int (canFrame.data[4]) == 9); 180 | REQUIRE (int (canFrame.data[5]) == 10); 181 | REQUIRE (int (canFrame.data[6]) == 11); 182 | REQUIRE (int (canFrame.data[7]) == 12); 183 | } 184 | 185 | if (calledTimes == 2) { 186 | REQUIRE (int (canFrame.dlc) == 8); 187 | REQUIRE (int (canFrame.data[0]) == 0x22); 188 | REQUIRE (int (canFrame.data[1]) == 13); 189 | REQUIRE (int (canFrame.data[2]) == 14); 190 | REQUIRE (int (canFrame.data[3]) == 15); 191 | REQUIRE (int (canFrame.data[4]) == 16); 192 | REQUIRE (int (canFrame.data[5]) == 17); 193 | REQUIRE (int (canFrame.data[6]) == 18); 194 | REQUIRE (int (canFrame.data[7]) == 19); 195 | } 196 | 197 | //... 198 | 199 | if (calledTimes == 15) { 200 | REQUIRE (int (canFrame.dlc) == 8); 201 | REQUIRE (int (canFrame.data[0]) == 0x2f); 202 | REQUIRE (int (canFrame.data[1]) == 104); 203 | REQUIRE (int (canFrame.data[2]) == 105); 204 | REQUIRE (int (canFrame.data[3]) == 106); 205 | REQUIRE (int (canFrame.data[4]) == 107); 206 | REQUIRE (int (canFrame.data[5]) == 108); 207 | REQUIRE (int (canFrame.data[6]) == 109); 208 | REQUIRE (int (canFrame.data[7]) == 110); 209 | } 210 | 211 | // ... 212 | 213 | if (calledTimes == 31) { 214 | REQUIRE (int (canFrame.dlc) == 8); 215 | REQUIRE (int (canFrame.data[0]) == 0x2f); 216 | REQUIRE (int (canFrame.data[1]) == 216); 217 | REQUIRE (int (canFrame.data[2]) == 217); 218 | REQUIRE (int (canFrame.data[3]) == 218); 219 | REQUIRE (int (canFrame.data[4]) == 219); 220 | REQUIRE (int (canFrame.data[5]) == 220); 221 | REQUIRE (int (canFrame.data[6]) == 221); 222 | REQUIRE (int (canFrame.data[7]) == 222); 223 | } 224 | 225 | // ... 226 | 227 | if (calledTimes == 585) { // last CAN frame to send 228 | REQUIRE (int (canFrame.data[0]) == 0x29); 229 | REQUIRE (int (canFrame.data[1]) == 254); 230 | REQUIRE (int (canFrame.dlc) == 2); 231 | } 232 | 233 | ++calledTimes; 234 | return true; 235 | }); 236 | 237 | std::vector message (4095); 238 | uint8_t v = 0; 239 | 240 | for (uint8_t &b : message) { 241 | b = v++; 242 | } 243 | 244 | tp.send (Address (0x00, 0x00), message); 245 | tp.run (); // IDLE -> SEND_FIRST_FRAME 246 | tp.run (); // SEND_FIRST_FRAME -> RECEIVE_FLOW_FRAME 247 | tp.onCanNewFrame (CanFrame (0x00, true, 0x30, 0x00, 0x00)); // RECEIVE_FLOW_FRAME -> SEND_CONSECUTIVE_FRAME 248 | tp.run (); // SEND_CONSECUTIVE_FRAME -> more consecutive frames 249 | 250 | while (tp.isSending ()) { 251 | tp.run (); 252 | } 253 | 254 | REQUIRE (calledTimes == 586); 255 | } 256 | 257 | TEST_CASE ("tx 4096B", "[send]") 258 | { 259 | { 260 | auto tp = create ({0, 0}, [] (auto const & /*unused*/) {}); 261 | // Move 262 | REQUIRE (tp.send (Address (0x67, 0x89), std::vector (4096)) == false); 263 | } 264 | 265 | { 266 | auto tp = create ({0, 0}, [] (auto const & /*unused*/) {}); 267 | // Copy 268 | auto v = std::vector (4096); 269 | REQUIRE (tp.send (Address (0x67, 0x89), v) == false); 270 | } 271 | 272 | { 273 | auto tp = create ({0, 0}, [] (auto const & /*unused*/) {}); 274 | // Reference 275 | auto v = std::vector (4096); 276 | auto r = std::ref (v); 277 | REQUIRE (tp.send (Address (0x67, 0x89), r) == false); 278 | } 279 | } 280 | 281 | // Not sure about this one 282 | TEST_CASE ("tx 1B timeout", "[send]") 283 | { 284 | // bool called = false; 285 | // auto tp = create ( 286 | // {0, 0}, [] (auto const & /*unused*/) {}, 287 | // [&called] (auto const &canFrame) { 288 | // called = true; 289 | // usleep (1600 * 1000); // 1.6s which is more than any timeout in the library 290 | // return true; 291 | // }); 292 | 293 | // REQUIRE (!tp.send (Address (0x67, 0x89), {0x55})); 294 | // REQUIRE (called); 295 | } 296 | 297 | TEST_CASE ("tx 4096B ETL", "[send]") 298 | { 299 | using MyIsoMessage = etl::vector; 300 | 301 | int calledTimes = 0; 302 | auto tp = create ( 303 | {0, 0}, [] (auto const & /* unused*/) {}, 304 | [&calledTimes] (auto const &canFrame) { 305 | // 1. It should send a first frame 306 | if (calledTimes == 0) { 307 | REQUIRE (int (canFrame.data[0]) == 0x10); // FIRST FRAME 308 | REQUIRE (int (canFrame.data[1]) == 13); // 13B length 309 | REQUIRE (int (canFrame.data[2]) == 0); // First byte 310 | REQUIRE (int (canFrame.data[3]) == 1); 311 | REQUIRE (int (canFrame.data[4]) == 2); 312 | REQUIRE (int (canFrame.data[5]) == 3); 313 | REQUIRE (int (canFrame.data[6]) == 4); 314 | REQUIRE (int (canFrame.data[7]) == 5); 315 | ++calledTimes; 316 | return true; 317 | } 318 | 319 | // 2. It should send a consecutive frame 320 | if (calledTimes == 1) { 321 | REQUIRE (int (canFrame.data[0]) == 0x21); // CONSECUTIVE FRAME, serial number 1 322 | REQUIRE (int (canFrame.data[1]) == 6); 323 | REQUIRE (int (canFrame.data[2]) == 7); 324 | REQUIRE (int (canFrame.data[3]) == 8); 325 | REQUIRE (int (canFrame.data[4]) == 9); 326 | REQUIRE (int (canFrame.data[5]) == 10); 327 | REQUIRE (int (canFrame.data[6]) == 11); 328 | REQUIRE (int (canFrame.data[7]) == 12); 329 | ++calledTimes; 330 | return true; 331 | } 332 | 333 | return false; 334 | }); 335 | 336 | tp.send (Address (0x00, 0x00), MyIsoMessage{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}); 337 | tp.run (); // IDLE -> SEND_FIRST_FRAME 338 | tp.run (); // SEND_FIRST_FRAME -> RECEIVE_FLOW_FRAME 339 | tp.onCanNewFrame (CanFrame (0x00, true, 0x30)); // RECEIVE_FLOW_FRAME -> SEND_CONSECUTIVE_FRAME 340 | tp.run (); // SEND_CONSECUTIVE_FRAME -> DONE 341 | 342 | while (tp.isSending ()) { 343 | tp.run (); 344 | } 345 | 346 | REQUIRE (calledTimes == 2); 347 | } 348 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What it is 2 | This library implements [ISO 15765-2](https://en.wikipedia.org/wiki/ISO_15765-2) transport layer known also as CAN bus ISO TP or simply *Transport Protocol*. It was developed with microcontrollers in mind, but was also tested on a Linux box (although on a Linux system there is a [better option](https://github.com/hartkopp/can-isotp)). 3 | 4 | ## Notes 5 | Whenever I refer to some cryptic paragraph numer in this document or in the source code I have **ISO 15765-2 First edition (2004-10-15)** on mind. This PDF id widely available in the Net though I'm not sure about legality of this stuff, so I'm not including it in the repo. Normally an ISO document like that costs around $150. 6 | 7 | This library is influenced by and was tested against [python-can-isotp](https://github.com/pylessard/python-can-isotp). 8 | 9 | # Using the library 10 | ## Building 11 | This library is header only, but you can build unit-tests and other simple examples, which can help you understand how to use the library in case of some trouble. This is a possible scenario of achieving this: 12 | 13 | ```sh 14 | git clone --recurse-submodules git@github.com:iwasz/cpp-can-isotp.git 15 | mkdir -p cpp-can-isotp/build 16 | cd cpp-can-isotp/build 17 | cmake -GNinja .. 18 | ninja 19 | 20 | # Run the tests 21 | test/unit-test/unit-test 22 | ``` 23 | In case of trouble with updating submodules, refer to [this stack overflow question](https://stackoverflow.com/questions/1030169/easy-way-to-pull-latest-of-all-git-submodules) (like I do everytime I deal with this stuff :D). 24 | 25 | ## Dependencies 26 | All dependencies are provided as git submodules: 27 | 28 | Run-time dependencies (libraries bundled with the code in deps directory as git submodules) 29 | * [etl](https://www.etlcpp.com/map.html) - etl_profile.h is required to be available somewhere in your include path. You can copy one from ```test/example``` for starters. 30 | * [GSL](https://github.com/microsoft/GSL) 31 | * C++17 (```if constexpr```). 32 | 33 | Unit tests 34 | * [Catch2](https://github.com/catchorg/Catch2) 35 | * [fmt](https://github.com/fmtlib/fmt) 36 | 37 | ## Using in your project 38 | Just 39 | 40 | ```cpp 41 | #include "TransportProtocol.h" 42 | ``` 43 | 44 | ## Instantiating 45 | First you have to instantiate the ```TransportProtocol``` class which encapsulates the protocol state. TransportProtocol is a class template and can be customized depending on underlying CAN-bus implementation, memory constraints, error (exception) handling sheme and time related stuff. This makes the API of TransportProtocol a little bit verbose, but also enables one to use it on a *normal* computer (see ```socket-test``` for Linux example) as well as on microcontrollers which this library was meant for at the first place. 46 | 47 | ```cpp 48 | #include "LinuxCanFrame.h" 49 | #include "TransportProtocol.h" 50 | 51 | /// ... 52 | 53 | using namespace tp; 54 | int socketFd = createSocket (); // (1) 55 | 56 | auto tp = create ( // (2) 57 | Address{0x789ABC, 0x123456}, // (3) 58 | [] (auto const &iso) { fmt::print ("Message size : {}\n", iso.size ()); }, // (4) 59 | [socketFd] (auto const &frame) { 60 | if (!sendSocket (socketFd, frame)) { // (5) 61 | fmt::print ("Error\n"); 62 | return false; 63 | } 64 | 65 | return true; 66 | }); 67 | 68 | listenSocket (socketFd, [&tp] (auto const &frame) { tp.onCanNewFrame (frame); }); // (6) 69 | ``` 70 | The code you see above is more or less all that's needed for **receiving** ISO-TP messages. In (1) we somehow connect to the underlying CAN-bus subsystem and then, using ```socketFd``` we are able to send and receive raw CAN-frames (see examples). 71 | 72 | ## Callbacks 73 | Callback is the second parameter to ```create``` function, and it can have 3 different forms. The simplest (called *simple* througout this document and the source code) is: 74 | 75 | ```cpp 76 | void indication (tp::IsoMessage const &msg) { /* ... */ } 77 | 78 | // Example usage: 79 | auto tp = tp::create (tp::Address{0x789ABC, 0x123456}, indication, socketSend); 80 | ``` 81 | 82 | This one implements ```N_USData.indication``` (see par. 5.2.4) and gets called whan new ISO message was successfully assembled, or in case of an error to inform the user, that current ISO message could not be assembled fully. In the latter case, the ```msg``` argument will be empty, but no other further information on the error will be available. Of course name ```indication``` is used here as an example, and even a lambda can be used (in fact in unit tests lambdas are used almost exclusively). 83 | 84 | The next one is called an *advanced* callback, because it has more parameters: 85 | 86 | ```cpp 87 | void indication (tp::Address const &a, tp::IsoMessage const &msg, tp::Result res) { /* ... */ } 88 | ``` 89 | 90 | Meaning of the parameters is : 91 | 92 | 1. Address ```a``` is the address of a peer who sent the message. Depending on the addressing scheme ```a.getTxId ()``` or ```a.getTargetAddress ()``` will be of interest to the user. See paragraph on addresses. 93 | 2. Message ```msg``` is the ISO message (in case of a success) or empty if there was an error. 94 | 3. Result ```res``` will have value ```Result::N_OK``` in case of a success or something other if case of a failed transmission. 95 | 96 | Lastly there is *advancedMethod* or *full* callback (again, those are the terms used in the unit tests and througout the comments in the source) which not only implements ```N_USData.indication``` but also ```N_USData.confirm``` and N_USData_FF.indication: 97 | 98 | ```cpp 99 | class FullCallback { 100 | public: 101 | void indication (tp::Address const &address, std::vector const &isoMessage, tp::Result result) {} 102 | void confirm (tp::Address const &address, tp::Result result) {} 103 | void firstFrameIndication (tp::Address const &address, uint16_t len) {} 104 | }; 105 | 106 | // Example usage: 107 | auto tp = tp::create (tp::Address{0x789ABC, 0x123456}, FullCallback (), socketSend); 108 | ``` 109 | 110 | # Addressing 111 | Addressing is somewhat vaguely described in the 2004 ISO document I have, so the best idea I had (after long head scratching) was to mimic the python-can-isotp library which I test my library against. In this API an address has a total of 5 numeric values representing various addresses, and another two types (target address type N_TAtype and the Mtype which stands for **TODO I forgot**). These numeric properties of an address object are: 112 | * rxId 113 | * txId 114 | * sourceAddress 115 | * targetAddress 116 | * networkAddressExtension 117 | 118 | The important thing to note here is that not all of them are used at the same time but rather, depending on addressing encoder (i.e. one of the addressing modes) used, a subset is used. 119 | 120 | # Platform speciffic remarks 121 | ## Arduino 122 | I successfully ported and tested this library to Arduino (see examples directory). Currently only [autowp/arduino-mcp2515](https://github.com/autowp/arduino-mcp2515) CAN implementation is supported. Be sure to experiment with separation time (use ```TransportProtocol::setSeparationTime```) to be sure that your board can keep up with receiving fast CAN frames bursts. 123 | 124 | # (part of the tutorial) 125 | ISO messages can be moved, copied or passed by reference_wrapper (std::ref) if move semantics arent implemented for your ISO message class. 126 | 127 | # Design decissions 128 | * I don't use exceptions because on a Coretex-M target enabling them increased the binary size by 13kB (around 10% increase). I use error codes (?) in favour of a error handler only because cpp-core-guidelines doest that. 129 | 130 | # TODOs, changes 131 | - [x] NO! Even when sending we must be able to receive a flow control frame. make specialization for void (and/or) 0-sized isoMessages. Such an implementation would be able to send only. 132 | - [ ] Describe (in this README) various callback options! 133 | - [ ] Once again rethink ```TransportProtocol::send``` interface. Previously it had pass-by-value agrgument, now I switched (switched back?) to universal-reference. 134 | - [ ] Make section here in the README about passing IsoMessages to ```TransportProtocol::send```. Show lvl, rvr (use std::move explicitly to make a point), and std::ref. 135 | - [ ] Test errorneus sequences of canFrames during assemblying segemnted messages. Make an unit test of that. For example on slower receivers some can frames can be lost, and this fact should be detected and reported to the user. 136 | - [ ] Add Stm32 example. 137 | - [ ] Move ```example``` from test to root, rename to ```examples```. Add Ardiono example with ino extension. 138 | - [ ] Extend Linux example, implement CAN interface properly using boost::asio. 139 | - [ ] Add Arduino example 140 | - [ ] Add an introduction to TP addressing in this README. 141 | - [x] Handle ISO messages that are constrained to less than maximum 4095B allowed by the ISO document. 142 | - [x] Test when ISO message is size-constrained. 143 | - [x] Test etl::vector ISO messages. 144 | - [x] Use valgrind in the unit-test binary from time to time. 145 | - [ ] Maybe optimize, but not so important. 146 | - [x] It is possible to define a TP object without a default address and then use its send method also without an address. This way you are sending a message into oblivion. Have it sorted out. 147 | - [ ] Get rid of all warinigs and c-tidy issues. 148 | - [x] Check if separationTime and blockSize received from a peer is taken into account during sending (check both sides of communication BS is faulty for sure, no flow frame is sent other than first one). 149 | - [x] blockSize is hardcoded to 8 for testing purposes. Revert to 0. 150 | - [ ] If errors occur during multi frame message receiving, the isoMessage should be removed (eventually. Probably some timeouts are mentioned in the ISO). Now it is not possible to receive second message if first has failed to be received entirely. 151 | - [x] Check if return value from sendFrame is taken into account. 152 | - [x] Implement all types of addressing. 153 | - [ ] Use some better means of unit testing. Test time dependent calls, maybe use some clever unit testing library like trompeleoleil for mocking. 154 | - [x] Test crosswise connected objects. They should be able to speak to each other. 155 | - [x] Test this library with python-can-isotp. 156 | - [ ] Add blocking API. 157 | - [ ] Test this api with std::threads. 158 | - [x] Implement FF parameters : BS and STime 159 | - [x] verify with the ISO pdf whether everything is implemented, and what has to be implemented. 160 | - [ ] Redesign API - I (as a user) hate being forced to provide dozens of arguyments upon construction that I don't really care about and do not use them. In this particular case my biggest concern is the create function and callbacks that it takes. 161 | - [x] Implement all enums that can be found in the ISO document. 162 | - [x] ~~Encapsulate more functionality into CanFrameWrapper. Like gettype, get length, getSerialnumber etc.~~ 163 | - [ ] (?) Include a note about N_As and NAr timeouts in the README.md. User shall check if sending a single CAN frame took les than 1000ms + 50%. He should return false in that case, true otherwise. 164 | - [ ] Address all TODOs in the code. 165 | - [x] Get rid of homeberew list, use etl. 166 | - [x] Test instantiation and usage with other CanFrame type 167 | - [x] Test instantiation and usage with other IsoMessage type 168 | - [x] Test flow control. 169 | - [ ] Communication services (page 3): 170 | - [x] N_USData.request (address, message) 171 | - [x] N_USData.confirm (address, result) <- request (this above an only this) completed successfully or not. 172 | - [X] N_USData_FF.indication (address, LENGTH) <- callback that first frame was received. It tells what is the lenghth of expected message 173 | - [x] N_USData.indication (address, Message, result) - after Sf or after multi-can-message. Indicates that new data has arrived. 174 | - [x] N_ChangeParameter.request (faddress, key, value) - requests a parameter change in peer or locally, I'm not sure. 175 | - [x] N_ChangeParameter.confirm (address, key, result). 176 | - [x] N_ChangeParameter.request and N_ChangeParameter.confirm are optional. Fixed values may be used instead, and this is the way to go I think. They are in fact hardcoded to 0 (best performance) in sendFlowFrame (see comment). 177 | - [x] Parameters (fixed or changeable) are : STmin and BS. Both hardcoded to 0. 178 | - [x] 5.2.3 and 5.2.4 when to send an indication and ff indication. 179 | - [x] enum N_Result 180 | - [X] Flow control during transmission (chapter 6.3 pages 12, 13, 14). 181 | - [x] Reading BS and STmin from FC frame received aftrer sending FF. 182 | - [x] Waiting for FC between blocks of size BS CAN frames. 183 | - [x] Reading this incoming FC and deciding what to do next. 184 | - [x] If CTS, resume normally, 185 | - [x] WAIT - wait (how much to wait?), 186 | - [x] If WAIT is received more than N_WFTmax times then fail (this also means, that FC can be received few times ina row). 187 | - [x] OVFLW - what to do then? 188 | - [x] Delay of STmin between CFs 189 | - [x] Protocol data units : create some factory and polymorphic types berived from CanFrameWrapper 190 | - [x] Timing 191 | - [x] Addressing 192 | - [x] Address information is included in every CAN frame whether FF, CF, FC or SF 193 | - [x] normal (uses arbitration ID, no constraints on the value) 194 | - [x] 11b 195 | - [x] 29b 196 | - [x] normal fixed (uses arbitration ID, further requirements as to how to encode this address into the arbitration ID). 29b only 197 | - [x] physical 198 | - [x] functional 199 | - [x] extended. Like normal, but first data byte contains targetAddress, which is absent in arbitration ID. 200 | - [x] 11b 201 | - [x] 29b 202 | - [x] mixed 203 | - [x] 11 204 | - [x] 29 205 | - [x] physical 206 | - [x] functional 207 | - [ ] Unexpected N_PDU 208 | - [ ] implent (if not imlenmented already) 209 | - [ ] test 210 | - [x] Get rid of dynamic allocation, because there is one. 211 | - [ ] Get rid of non English comments. 212 | - [ ] Finish this TODO 213 | - [ ] Calls like this : ```indication (*theirAddress, {}, Result::N_UNEXP_PDU);``` are potentially inefficient if IsoMessageT internally allocates on the stack (etl::vector for example). Possible solution would be to pass by pointer and pass nullptr in such cases, but that would be inconvenient for the end user. 214 | - [ ] ~~Problem with sequence numbers. Sometimes python can-isotp library would dump the following error message (sequence numbers are various)~~ This was a problem on the other part of the connection. Python app has had synchronisation problems: 215 | 216 | INFO:root:Preparing the telemetry... 217 | WARNING:root:IsoTp error happened : WrongSequenceNumberError - Received a ConsecutiveFrame with wrong SequenceNumber. Expecting 0x2, Received 0x3 218 | WARNING:root:IsoTp error happened : UnexpectedConsecutiveFrameError - Received a ConsecutiveFrame while reception was idle. Ignoring 219 | WARNING:isotp:Received a ConsecutiveFrame with wrong SequenceNumber. Expecting 0x2, Received 0x3 220 | WARNING:isotp:Received a ConsecutiveFrame while reception was idle. Ignoring 221 | WARNING:root:Device thermometer_1 is UNSTABLE 222 | ERROR:root:Exception in prepareTelemetry task 223 | Traceback (most recent call last): 224 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/telemetry.py", line 82, in prepareTelemetry 225 | await aggregateStore() 226 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/engine.py", line 34, in aggregateStore 227 | cachedValues = await cacheValuesForDevice( 228 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/engine.py", line 187, in cacheValuesForDevice 229 | value = await device.request(deviceName, propertyName) 230 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/device/device.py", line 55, in request 231 | return await getattr(self, command)() 232 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/device/external/oneWireSensors.py", line 70, in getTemperature 233 | data = await onewire.readBytes(self.port, 9) 234 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/core/onewire.py", line 68, in readBytes 235 | decoded = await messagePack.measurementsRequest( 236 | File "/home/iwasz/workspace/nbox/nbox-raspi/src/core/messagePack.py", line 49, in measurementsRequest 237 | return await asyncio.wait_for( 238 | File "/usr/lib/python3.8/asyncio/tasks.py", line 490, in wait_for 239 | raise exceptions.TimeoutError() 240 | asyncio.exceptions.TimeoutError 241 | INFO:root:Sending current telemetry... 242 | 243 | # License 244 | [MIT](https://opensource.org/licenses/MIT) 245 | 246 | Copyright 2021 Łukasz Iwaszkiewicz 247 | 248 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 249 | 250 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 251 | 252 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /test/unit-test/01RecvTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace tp; 15 | 16 | TEST_CASE ("rx 1B", "[recv]") 17 | { 18 | bool called = false; 19 | auto tp = create ({0, 0}, [&called] (auto &&tm) { 20 | called = true; 21 | REQUIRE (tm.size () == 1); 22 | REQUIRE (tm[0] == 0x67); 23 | }); 24 | 25 | tp.onCanNewFrame (CanFrame (0x00, true, 0x01, 0x67)); 26 | REQUIRE (called); 27 | } 28 | 29 | TEST_CASE ("rx 7B", "[recv]") 30 | { 31 | bool called = false; 32 | auto tp = create ({0, 0}, [&called] (auto &&tm) { 33 | called = true; 34 | REQUIRE (tm.size () == 7); 35 | REQUIRE (tm[0] == 0); 36 | REQUIRE (tm[1] == 1); 37 | REQUIRE (tm[2] == 2); 38 | REQUIRE (tm[3] == 3); 39 | REQUIRE (tm[4] == 4); 40 | REQUIRE (tm[5] == 5); 41 | REQUIRE (tm[6] == 6); 42 | }); 43 | 44 | tp.onCanNewFrame (CanFrame (0x00, true, 0x07, 0, 1, 2, 3, 4, 5, 6)); 45 | REQUIRE (called); 46 | } 47 | 48 | TEST_CASE ("rx 8B", "[recv]") 49 | { 50 | bool called = false; 51 | bool flow = false; 52 | auto tp = create ( 53 | {0, 0}, 54 | [&called] (auto &&tm) { 55 | called = true; 56 | REQUIRE (tm.size () == 8); 57 | REQUIRE (tm[0] == 0); 58 | REQUIRE (tm[1] == 1); 59 | REQUIRE (tm[2] == 2); 60 | REQUIRE (tm[3] == 3); 61 | REQUIRE (tm[4] == 4); 62 | REQUIRE (tm[5] == 5); 63 | REQUIRE (tm[6] == 6); 64 | REQUIRE (tm[7] == 7); 65 | }, 66 | [&flow] (auto &&canFrame) { 67 | flow = true; 68 | REQUIRE (true); 69 | return true; 70 | }); 71 | 72 | tp.onCanNewFrame (CanFrame (0x00, true, 0x10, 8, 0, 1, 2, 3, 4, 5)); 73 | tp.onCanNewFrame (CanFrame (0x00, true, 0x21, 6, 7)); 74 | 75 | REQUIRE (called); 76 | REQUIRE (flow); 77 | } 78 | 79 | TEST_CASE ("rx 13B", "[recv]") 80 | { 81 | bool called = false; 82 | bool flow = false; 83 | auto tp = create ( 84 | {0, 0}, 85 | [&called] (auto &&tm) { 86 | called = true; 87 | REQUIRE (tm.size () == 13); 88 | REQUIRE (tm[0] == 0); 89 | REQUIRE (tm[1] == 1); 90 | REQUIRE (tm[2] == 2); 91 | REQUIRE (tm[3] == 3); 92 | REQUIRE (tm[4] == 4); 93 | REQUIRE (tm[5] == 5); 94 | REQUIRE (tm[6] == 6); 95 | REQUIRE (tm[7] == 7); 96 | REQUIRE (tm[8] == 8); 97 | REQUIRE (tm[9] == 9); 98 | REQUIRE (tm[10] == 10); 99 | REQUIRE (tm[11] == 11); 100 | REQUIRE (tm[12] == 12); 101 | }, 102 | [&flow] (auto &&canFrame) { 103 | flow = true; 104 | REQUIRE (canFrame.data[0] == 0x30); 105 | return true; 106 | }); 107 | 108 | // 13B fits into 2 CanFrames 109 | tp.onCanNewFrame (CanFrame (0x00, true, 0x10, 13, 0, 1, 2, 3, 4, 5)); 110 | tp.onCanNewFrame (CanFrame (0x00, true, 0x21, 6, 7, 8, 9, 10, 11, 12)); 111 | 112 | REQUIRE (called); 113 | REQUIRE (flow); 114 | } 115 | 116 | TEST_CASE ("rx 4095B", "[recv]") 117 | { 118 | bool called = false; 119 | bool flow = false; 120 | 121 | auto tp = create ( 122 | {0, 0}, 123 | [&called] (auto &&tm) { 124 | called = true; 125 | REQUIRE (tm.size () == 4095); 126 | REQUIRE (tm[0] == 0); 127 | REQUIRE (tm[1] == 1); 128 | REQUIRE (tm[2] == 2); 129 | REQUIRE (tm[3] == 3); 130 | REQUIRE (tm[4] == 4); 131 | REQUIRE (tm[5] == 5); 132 | REQUIRE (tm[6] == 6); 133 | REQUIRE (tm[7] == 7); 134 | REQUIRE (tm[8] == 8); 135 | REQUIRE (tm[9] == 9); 136 | REQUIRE (tm[10] == 10); 137 | REQUIRE (tm[11] == 11); 138 | REQUIRE (tm[12] == 12); 139 | REQUIRE (tm[13] == 13); 140 | //... 141 | REQUIRE (tm[4087] == 247); 142 | REQUIRE (tm[4088] == 248); 143 | REQUIRE (tm[4089] == 249); 144 | REQUIRE (tm[4090] == 250); 145 | REQUIRE (tm[4091] == 251); 146 | REQUIRE (tm[4092] == 252); 147 | REQUIRE (tm[4093] == 253); 148 | REQUIRE (tm[4094] == 254); 149 | }, 150 | [&flow] (auto &&canFrame) { 151 | flow = true; 152 | REQUIRE (canFrame.data[0] == 0x30); 153 | return true; 154 | }); 155 | 156 | tp.onCanNewFrame (CanFrame (0x00, true, 0x1f, 0xff, 0, 1, 2, 3, 4, 5)); 157 | 158 | // It happens so 4095 / 7 == 585 159 | int j = 6; // We stoped at 5 in the FIRST_FRAME, so we continue starting from 6 160 | int sn = 1; 161 | for (int i = 0; i < 4095 / 7 - 1; ++i, j += 7, ++sn) { 162 | j %= 256; 163 | sn %= 16; 164 | tp.onCanNewFrame (CanFrame (0x00, true, 0x20 | sn, j, j + 1, j + 2, j + 3, j + 4, j + 5, j + 6)); 165 | } 166 | 167 | j %= 256; 168 | sn %= 16; 169 | // First frame had 6B, then in the loop we add 584*7 = 4088, and then only 1. 6+4088+1 == 4095 == 0xfff 170 | tp.onCanNewFrame (CanFrame (0x00, true, 0x20 | sn, j)); 171 | 172 | REQUIRE (called); 173 | REQUIRE (flow); 174 | } 175 | 176 | /** 177 | * This tests one particular problem I've had. 178 | * MessagePack data : 179 | * [131, 163, 114, 101, 113, 1, 164, 97, 100, 100, 114, 0, 163, 118, 97, 108, 1] 180 | * 181 | * means: 182 | * { 183 | * "req": 1, 184 | * "addr": 0, 185 | * "val": 1 186 | * } 187 | * 188 | * In the CAN layer it should look like this: 189 | * 190 | * slcan0 18DA2211 [8] 10 11 83 A3 72 65 71 01 191 | * slcan0 18DA1122 [3] 30 00 01 192 | * slcan0 18DA2211 [8] 21 A4 61 64 64 72 00 A3 193 | * slcan0 18DA2211 [5] 22 76 61 6C 01 194 | */ 195 | TEST_CASE ("rx MessagePack", "[recv]") 196 | { 197 | bool called = false; 198 | bool flow = false; 199 | 200 | static constexpr size_t ISO_MESSAGE_SIZE = 512; 201 | using MyIsoMessage = etl::vector; 202 | auto tp = create ( 203 | Address{0x00, 0x00, 0x22, 0x11}, 204 | [&called] (auto const &tm) { 205 | called = true; 206 | REQUIRE (tm.size () == 17); 207 | REQUIRE (tm[0] == 131); 208 | REQUIRE (tm[1] == 163); 209 | REQUIRE (tm[2] == 114); 210 | REQUIRE (tm[3] == 101); 211 | REQUIRE (tm[4] == 113); 212 | REQUIRE (tm[5] == 1); 213 | REQUIRE (tm[6] == 164); 214 | REQUIRE (tm[7] == 97); 215 | REQUIRE (tm[8] == 100); 216 | REQUIRE (tm[9] == 100); 217 | REQUIRE (tm[10] == 114); 218 | REQUIRE (tm[11] == 0); 219 | REQUIRE (tm[12] == 163); 220 | REQUIRE (tm[13] == 118); 221 | REQUIRE (tm[14] == 97); 222 | REQUIRE (tm[15] == 108); 223 | REQUIRE (tm[16] == 1); 224 | }, 225 | [&flow] (auto &&canFrame) { 226 | flow = true; 227 | REQUIRE (canFrame.data[0] == 0x30); 228 | return true; 229 | }); 230 | 231 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x10, 0x11, 0x83, 0xA3, 0x72, 0x65, 0x71, 0x01)); 232 | // Here it responds with (it should respond) 18DA1122 [3] 30 00 01 233 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x21, 0xA4, 0x61, 0x64, 0x64, 0x72, 0x00, 0xA3)); 234 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x22, 0x76, 0x61, 0x6C, 0x01)); 235 | 236 | REQUIRE (called); 237 | REQUIRE (flow); 238 | } 239 | 240 | /** 241 | * Another usecase from my work that failed. 242 | * Two consecutive transactions (request + response, request2 + response2). The 243 | * problem is response2 is not getting through. 244 | */ 245 | TEST_CASE ("MessagePack transaction", "[recv]") 246 | { 247 | using Vec = std::vector; 248 | 249 | int rxStage = 0; 250 | int txStage = 0; 251 | 252 | static constexpr size_t ISO_MESSAGE_SIZE = 128; 253 | using MyIsoMessage = etl::vector; 254 | auto tp = create ( 255 | Address{0x00, 0x00, 0x22, 0x11}, 256 | [&rxStage] (auto const &tm) { 257 | if (rxStage == 0) { 258 | ++rxStage; 259 | REQUIRE (tm.size () == 17); 260 | REQUIRE (tm[0] == 131); 261 | REQUIRE (tm[1] == 163); 262 | REQUIRE (tm[2] == 114); 263 | REQUIRE (tm[3] == 101); 264 | REQUIRE (tm[4] == 113); 265 | REQUIRE (tm[5] == 1); 266 | REQUIRE (tm[6] == 164); 267 | REQUIRE (tm[7] == 97); 268 | REQUIRE (tm[8] == 100); 269 | REQUIRE (tm[9] == 100); 270 | REQUIRE (tm[10] == 114); 271 | REQUIRE (tm[11] == 0); 272 | REQUIRE (tm[12] == 163); 273 | REQUIRE (tm[13] == 118); 274 | REQUIRE (tm[14] == 97); 275 | REQUIRE (tm[15] == 108); 276 | REQUIRE (tm[16] == 1); 277 | } 278 | if (rxStage == 1) { 279 | ++rxStage; 280 | REQUIRE (tm.size () == 17); 281 | REQUIRE (tm[0] == 131); 282 | REQUIRE (tm[1] == 163); 283 | REQUIRE (tm[2] == 114); 284 | REQUIRE (tm[3] == 101); 285 | REQUIRE (tm[4] == 113); 286 | REQUIRE (tm[5] == 1); 287 | REQUIRE (tm[6] == 164); 288 | REQUIRE (tm[7] == 97); 289 | REQUIRE (tm[8] == 100); 290 | REQUIRE (tm[9] == 100); 291 | REQUIRE (tm[10] == 114); 292 | REQUIRE (tm[11] == 0); 293 | REQUIRE (tm[12] == 163); 294 | REQUIRE (tm[13] == 118); 295 | REQUIRE (tm[14] == 97); 296 | REQUIRE (tm[15] == 108); 297 | REQUIRE (tm[16] == 1); 298 | } 299 | }, 300 | [&txStage] (auto &&canFrame) { 301 | if (txStage == 0) { 302 | ++txStage; 303 | REQUIRE (canFrame.data[0] == 0x30); 304 | REQUIRE (canFrame.data[1] == 0x00); 305 | REQUIRE (canFrame.data[2] == 0x00); 306 | } 307 | else if (txStage == 1) { 308 | ++txStage; 309 | REQUIRE (canFrame.data[0] == 0x10); 310 | REQUIRE (canFrame.data[1] == 0x24); 311 | REQUIRE (canFrame.data[2] == 0x83); 312 | REQUIRE (canFrame.data[3] == 0xa3); 313 | REQUIRE (canFrame.data[4] == 0x72); 314 | REQUIRE (canFrame.data[5] == 0x73); 315 | REQUIRE (canFrame.data[6] == 0x70); 316 | REQUIRE (canFrame.data[7] == 0x01); 317 | } 318 | else if (txStage == 2) { 319 | ++txStage; 320 | REQUIRE (Vec (canFrame.data.cbegin (), canFrame.data.cend ()) 321 | == Vec{0x21, 0xA3, 0x65, 0x72, 0x72, 0x02, 0xA3, 0x6D}); 322 | } 323 | else if (txStage == 3) { 324 | ++txStage; 325 | REQUIRE (Vec (canFrame.data.cbegin (), canFrame.data.cend ()) 326 | == Vec{0x22, 0x73, 0x67, 0xB4, 0x43, 0x6F, 0x6E, 0x6E}); 327 | } 328 | else if (txStage == 4) { 329 | ++txStage; 330 | REQUIRE (Vec (canFrame.data.cbegin (), canFrame.data.cend ()) 331 | == Vec{0x23, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20}); 332 | } 333 | else if (txStage == 5) { 334 | ++txStage; 335 | REQUIRE (Vec (canFrame.data.cbegin (), canFrame.data.cend ()) 336 | == Vec{0x24, 0x74, 0x69, 0x6D, 0x65, 0x64, 0x20, 0x6F}); 337 | } 338 | else if (txStage == 6) { 339 | ++txStage; 340 | 341 | REQUIRE (canFrame.data[0] == 0x25); 342 | REQUIRE (canFrame.data[1] == 0x75); 343 | REQUIRE (canFrame.data[2] == 0x74); 344 | } 345 | 346 | return true; 347 | }); 348 | 349 | // Request 1 (turn on the relay) 350 | txStage = 0; 351 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x10, 0x11, 0x83, 0xA3, 0x72, 0x65, 0x71, 0x01)); 352 | // Here it responds with 18DA1122 [3] 30 00 01 353 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x21, 0xA4, 0x61, 0x64, 0x64, 0x72, 0x00, 0xA3)); 354 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x22, 0x76, 0x61, 0x6C, 0x01)); 355 | 356 | // Response 1 (Modbus connection timeout) 357 | tp.send ({0x83, 0xA3, 0x72, 0x73, 0x70, 0x01, 0xA3, 0x65, 0x72, 0x72, 0x02, 0xA3, 0x6D, 0x73, 0x67, 0xB4, 0x43, 0x6F, 358 | 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x64, 0x20, 0x6F, 0x75, 0x74}); 359 | 360 | tp.run (); 361 | tp.run (); 362 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x30, 0x10, 0x00)); 363 | 364 | while (tp.isSending ()) { 365 | tp.run (); 366 | } 367 | 368 | // Request 2 (turn off the relay) 369 | txStage = 0; 370 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x10, 0x11, 0x83, 0xA3, 0x72, 0x65, 0x71, 0x01)); 371 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x21, 0xA4, 0x61, 0x64, 0x64, 0x72, 0x00, 0xA3)); 372 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x22, 0x76, 0x61, 0x6C, 0x00)); 373 | 374 | // Response 2 (Modbus connection timeout) - same as response 2 375 | tp.send ({0x83, 0xA3, 0x72, 0x73, 0x70, 0x01, 0xA3, 0x65, 0x72, 0x72, 0x02, 0xA3, 0x6D, 0x73, 0x67, 0xB4, 0x43, 0x6F, 376 | 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x69, 0x6D, 0x65, 0x64, 0x20, 0x6F, 0x75, 0x74}); 377 | 378 | tp.run (); 379 | tp.run (); 380 | tp.onCanNewFrame (CanFrame (0x18DA2211, true, 0x30, 0x10, 0x00)); 381 | 382 | while (tp.isSending ()) { 383 | tp.run (); 384 | } 385 | } 386 | -------------------------------------------------------------------------------- /src/Address.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "MiscTypes.h" 11 | 12 | namespace tp { 13 | 14 | static constexpr uint32_t MAX_11_ID = 0x7ff; 15 | static constexpr uint32_t MAX_29_ID = 0x1FFFFFFF; 16 | 17 | /** 18 | * This is the address class which represents ISO TP addresses. It lives in 19 | * slightly higher level of abstraction than addresses described in the ISO 20 | * document (N_AI) as it use the same variables for all 7 address types (localAddress 21 | * and remoteAddress). This are "address encoders" which further convert this 22 | * "Address" to appropriate data inside Can Frames. 23 | */ 24 | struct Address { 25 | 26 | enum class MessageType : uint8_t { 27 | DIAGNOSTICS, /// N_SA, N_TA, N_TAtype are used 28 | REMOTE_DIAGNOSTICS /// N_SA, N_TA, N_TAtype and N_AE are used 29 | }; 30 | 31 | /// N_TAtype Network Target Address Type 32 | enum class TargetAddressType : uint8_t { 33 | PHYSICAL, /// 1 to 1 communication supported for multiple and single frame communications 34 | FUNCTIONAL /// 1 to n communication (like broadcast?) is allowed only for single frame comms. 35 | }; 36 | 37 | Address () = default; 38 | 39 | Address (uint32_t rxId, uint32_t txId, MessageType mt = MessageType::DIAGNOSTICS, TargetAddressType tat = TargetAddressType::PHYSICAL) 40 | : rxId (rxId), txId (txId), messageType (mt), targetAddressType (tat) 41 | { 42 | } 43 | 44 | Address (uint32_t rxId, uint32_t txId, uint8_t sourceAddress, uint8_t targetAddress, MessageType mt = MessageType::DIAGNOSTICS, 45 | TargetAddressType tat = TargetAddressType::PHYSICAL) 46 | : rxId (rxId), txId (txId), sourceAddress (sourceAddress), targetAddress (targetAddress), messageType (mt), targetAddressType (tat) 47 | { 48 | } 49 | 50 | Address (uint32_t rxId, uint32_t txId, uint8_t sourceAddress, uint8_t targetAddress, uint8_t networkAddressExtension, 51 | MessageType mt = MessageType::DIAGNOSTICS, TargetAddressType tat = TargetAddressType::PHYSICAL) 52 | : rxId (rxId), 53 | txId (txId), 54 | sourceAddress (sourceAddress), 55 | targetAddress (targetAddress), 56 | networkAddressExtension (networkAddressExtension), 57 | messageType (mt), 58 | targetAddressType (tat) 59 | { 60 | } 61 | 62 | uint32_t getRxId () const { return this->rxId; } 63 | void setRxId (uint32_t rxId) { this->rxId = rxId; } 64 | 65 | uint32_t getTxId () const { return this->txId; } 66 | void setTxId (uint32_t txId) { this->txId = txId; } 67 | 68 | uint8_t getSourceAddress () const { return this->sourceAddress; } 69 | void setSourceAddress (uint8_t sourceAddress) { this->sourceAddress = sourceAddress; } 70 | 71 | uint8_t getTargetAddress () const { return this->targetAddress; } 72 | void setTargetAddress (uint8_t targetAddress) { this->targetAddress = targetAddress; } 73 | 74 | uint8_t getNetworkAddressExtension () const { return this->networkAddressExtension; } 75 | void setNetworkAddressExtension (uint8_t networkAddressExtension) { this->networkAddressExtension = networkAddressExtension; } 76 | 77 | MessageType getMessageType () const { return this->messageType; } 78 | void setMessageType (MessageType messageType) { this->messageType = messageType; } 79 | 80 | TargetAddressType getTargetAddressType () const { return this->targetAddressType; } 81 | void setTargetAddressType (TargetAddressType targetAddressType) { this->targetAddressType = targetAddressType; } 82 | 83 | private: 84 | uint32_t rxId{}; 85 | uint32_t txId{}; 86 | 87 | /// 5.3.2.2 N_SA encodes the network sender address. Valid both for DIAGNOSTICS and REMOTE_DIAGNOSTICS. 88 | uint8_t sourceAddress{}; 89 | 90 | /// 5.3.2.3 N_TA encodes the network target address. Valid both for DIAGNOSTICS and REMOTE_DIAGNOSTICS. 91 | uint8_t targetAddress{}; 92 | 93 | /// 5.3.2.5 N_AE Network address extension. 94 | uint8_t networkAddressExtension{}; 95 | 96 | /// 5.3.1 97 | MessageType messageType{MessageType::DIAGNOSTICS}; 98 | 99 | /// 5.3.2.4 100 | TargetAddressType targetAddressType{TargetAddressType::PHYSICAL}; 101 | }; // namespace tp 102 | 103 | inline bool operator< (Address const &a, Address const &b) 104 | { 105 | return a.getTxId () < b.getTxId () && a.getTargetAddress () < b.getTargetAddress () 106 | && a.getNetworkAddressExtension () < b.getNetworkAddressExtension (); 107 | } 108 | 109 | /****************************************************************************/ 110 | 111 | struct Normal11AddressEncoder { 112 | 113 | /** 114 | * Create an address from a received CAN frame. This is 115 | * the address which the remote party used to send the frame to us. 116 | */ 117 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 118 | { 119 | auto fId = f.getId (); 120 | 121 | if (f.isExtended () || fId > MAX_11_ID) { 122 | return {}; 123 | } 124 | 125 | return Address (0x00, fId); 126 | } 127 | 128 | /** 129 | * Store address into a CAN frame. This is the address of the remote party we want the message to get to. 130 | */ 131 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 132 | { 133 | if (a.getTxId () > MAX_11_ID) { 134 | return false; 135 | } 136 | 137 | f.setId (a.getTxId ()); 138 | f.setExtended (false); 139 | return true; 140 | } 141 | 142 | /// Implements address matching for this type of addressing. 143 | static bool matches (Address const &theirs, Address const &ours) { return theirs.getTxId () == ours.getRxId (); } 144 | }; 145 | 146 | /****************************************************************************/ 147 | 148 | struct Normal29AddressEncoder { 149 | 150 | /** 151 | * Create an address from a received CAN frame. This is 152 | * the address which the remote party used to send the frame to us. 153 | */ 154 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 155 | { 156 | auto fId = f.getId (); 157 | 158 | if (!f.isExtended () || fId > MAX_29_ID) { 159 | return {}; 160 | } 161 | return Address (0x00, fId); 162 | } 163 | 164 | /** 165 | * Store address into a CAN frame. This is the address of the remote party we want the message to get to. 166 | */ 167 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 168 | { 169 | if (a.getTxId () > MAX_29_ID) { 170 | return false; 171 | } 172 | 173 | f.setId (a.getTxId ()); 174 | f.setExtended (true); 175 | return true; 176 | } 177 | 178 | /// Implements address matching for this type of addressing. 179 | static bool matches (Address const &theirs, Address const &ours) { return theirs.getTxId () == ours.getRxId (); } 180 | }; 181 | 182 | /****************************************************************************/ 183 | 184 | struct NormalFixed29AddressEncoder { 185 | 186 | static constexpr uint32_t NORMAL_FIXED_29 = 0x018DA0000; 187 | static constexpr uint32_t NORMAL_FIXED_29_MASK = 0x1FFE0000; 188 | static constexpr uint32_t N_TA_MASK = 0x00000ff00; 189 | static constexpr uint32_t N_TATYPE_MASK = 0x10000; 190 | static constexpr uint32_t N_SA_MASK = 0x0000000ff; 191 | static constexpr uint32_t MAX_N = 0xff; /// Maximum value that can be stored in either N_TA, N_SA. 192 | 193 | /** 194 | * Create an address from a received CAN frame. This is 195 | * the address which the remote party used to send the frame to us. 196 | */ 197 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 198 | { 199 | auto fId = f.getId (); 200 | 201 | if (!f.isExtended () || bool ((fId & NORMAL_FIXED_29_MASK) != NORMAL_FIXED_29)) { 202 | return {}; 203 | } 204 | 205 | return Address (0x00, 0x00, fId & N_SA_MASK, (fId & N_TA_MASK) >> 8, Address::MessageType::DIAGNOSTICS, 206 | (bool (fId & N_TATYPE_MASK)) ? (Address::TargetAddressType::FUNCTIONAL) 207 | : (Address::TargetAddressType::PHYSICAL)); 208 | } 209 | 210 | /** 211 | * Store address into a CAN frame. This is the address of the remote party we want the message to get to. 212 | */ 213 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 214 | { 215 | f.setId (NORMAL_FIXED_29 | ((a.getTargetAddressType () == Address::TargetAddressType::FUNCTIONAL) ? (N_TATYPE_MASK) : (0)) 216 | | a.getTargetAddress () << 8 | a.getSourceAddress ()); 217 | 218 | f.setExtended (true); 219 | 220 | return true; 221 | } 222 | 223 | /// Implements address matching for this type of addressing. 224 | static bool matches (Address const &theirs, Address const &ours) { return theirs.getTargetAddress () == ours.getSourceAddress (); } 225 | }; 226 | 227 | /****************************************************************************/ 228 | 229 | struct Extended11AddressEncoder { 230 | 231 | static constexpr uint32_t MAX_TA = 0xff; 232 | 233 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 234 | { 235 | auto fId = f.getId (); 236 | 237 | if (f.isExtended () || fId > MAX_11_ID || f.getDlc () < 1) { 238 | return {}; 239 | } 240 | 241 | return Address (0x00, fId, 0x00, f.get (0)); 242 | } 243 | 244 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 245 | { 246 | if (a.getTxId () > MAX_11_ID) { 247 | return false; 248 | } 249 | 250 | f.setId (a.getTxId ()); 251 | f.setExtended (false); 252 | 253 | if (f.getDlc () < 1) { 254 | f.setDlc (1); 255 | } 256 | 257 | f.set (0, a.getTargetAddress ()); 258 | return true; 259 | } 260 | 261 | /// Implements address matching for this type of addressing. 262 | static bool matches (Address const &theirs, Address const &ours) 263 | { 264 | return theirs.getTxId () == ours.getRxId () && theirs.getTargetAddress () == ours.getSourceAddress (); 265 | } 266 | }; 267 | 268 | /****************************************************************************/ 269 | 270 | struct Extended29AddressEncoder { 271 | 272 | static constexpr uint32_t MAX_TA = 0xff; 273 | 274 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 275 | { 276 | auto fId = f.getId (); 277 | 278 | if (!f.isExtended () || fId > MAX_29_ID || f.getDlc () < 1) { 279 | return {}; 280 | } 281 | 282 | return Address (0x00, fId, 0x00, f.get (0)); 283 | } 284 | 285 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 286 | { 287 | if (a.getTxId () > MAX_29_ID) { 288 | return false; 289 | } 290 | 291 | f.setId (a.getTxId ()); 292 | f.setExtended (true); 293 | 294 | if (f.getDlc () < 1) { 295 | f.setDlc (1); 296 | } 297 | 298 | f.set (0, a.getTargetAddress ()); 299 | return true; 300 | } 301 | 302 | /// Implements address matching for this type of addressing. 303 | static bool matches (Address const &theirs, Address const &ours) 304 | { 305 | return theirs.getTxId () == ours.getRxId () && theirs.getTargetAddress () == ours.getSourceAddress (); 306 | } 307 | }; 308 | 309 | /****************************************************************************/ 310 | 311 | struct Mixed11AddressEncoder { 312 | 313 | /** 314 | * Create an address from a received CAN frame. This is 315 | * the address which the remote party used to send the frame to us. 316 | */ 317 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 318 | { 319 | if (f.isExtended () || f.getDlc () < 1 || f.getId () > MAX_11_ID) { 320 | return {}; 321 | } 322 | 323 | return Address (0x00, f.getId (), 0x00, 0x00, f.get (0), Address::MessageType::REMOTE_DIAGNOSTICS); 324 | } 325 | 326 | /** 327 | * Store address into a CAN frame. This is the address of the remote party we want the message to get to. 328 | */ 329 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 330 | { 331 | // if (a.messageType != Address::MessageType::REMOTE_DIAGNOSTICS) { 332 | // return false; 333 | // } 334 | if (a.getTxId () > MAX_11_ID) { 335 | return false; 336 | } 337 | 338 | f.setId (a.getTxId ()); 339 | 340 | if (f.getDlc () < 1) { 341 | f.setDlc (1); 342 | } 343 | 344 | f.set (0, a.getNetworkAddressExtension ()); 345 | f.setExtended (false); 346 | return true; 347 | } 348 | 349 | /// Implements address matching for this type of addressing. 350 | static bool matches (Address const &theirs, Address const &ours) 351 | { 352 | return theirs.getTxId () == ours.getRxId () && theirs.getNetworkAddressExtension () == ours.getNetworkAddressExtension (); 353 | } 354 | }; 355 | 356 | /****************************************************************************/ 357 | 358 | struct Mixed29AddressEncoder { 359 | 360 | static constexpr uint32_t PHYS_29 = 0x018Ce0000; 361 | static constexpr uint32_t FUNC_29 = 0x018Cd0000; 362 | static constexpr uint32_t PHYS_FUNC_29_MASK = 0x1FFf0000; 363 | static constexpr uint32_t N_TA_MASK = 0x00000ff00; 364 | static constexpr uint32_t N_SA_MASK = 0x0000000ff; 365 | 366 | /** 367 | * Create an address from a received CAN frame. This is 368 | * the address which the remote party used to send the frame to us. 369 | */ 370 | template static etl::optional
fromFrame (CanFrameWrapper const &f) 371 | { 372 | auto fId = f.getId (); 373 | 374 | if (!f.isExtended () || f.getDlc () < 1) { 375 | return {}; 376 | } 377 | 378 | if (bool ((fId & PHYS_FUNC_29_MASK) == PHYS_29)) { 379 | return Address (0x00, 0x00, fId & N_SA_MASK, (fId & N_TA_MASK) >> 8, f.get (0), Address::MessageType::REMOTE_DIAGNOSTICS, 380 | Address::TargetAddressType::PHYSICAL); 381 | } 382 | 383 | if (bool ((fId & PHYS_FUNC_29_MASK) == FUNC_29)) { 384 | return Address (0x00, 0x00, fId & N_SA_MASK, (fId & N_TA_MASK) >> 8, f.get (0), Address::MessageType::REMOTE_DIAGNOSTICS, 385 | Address::TargetAddressType::FUNCTIONAL); 386 | } 387 | 388 | return {}; 389 | } 390 | 391 | /** 392 | * Store address into a CAN frame. This is the address of the remote party we want the message to get to. 393 | */ 394 | template static bool toFrame (Address const &a, CanFrameWrapper &f) 395 | { 396 | // if (a.messageType != Address::MessageType::REMOTE_DIAGNOSTICS) { 397 | // return false; 398 | // } 399 | 400 | f.setId (((a.getTargetAddressType () == Address::TargetAddressType::PHYSICAL) ? (PHYS_29) : (FUNC_29)) 401 | | a.getTargetAddress () << 8 | a.getSourceAddress ()); 402 | 403 | if (f.getDlc () < 1) { 404 | f.setDlc (1); 405 | } 406 | 407 | f.set (0, a.getNetworkAddressExtension ()); 408 | f.setExtended (true); 409 | return true; 410 | } 411 | 412 | /// Implements address matching for this type of addressing. 413 | static bool matches (Address const &theirs, Address const &ours) 414 | { 415 | return theirs.getTargetAddress () == ours.getSourceAddress () 416 | && theirs.getNetworkAddressExtension () == ours.getNetworkAddressExtension (); 417 | } 418 | }; 419 | 420 | /** 421 | * Helper class 422 | */ 423 | template struct AddressTraitsBase { 424 | 425 | static constexpr uint8_t N_PCI_OFSET = (T::USING_EXTENDED) ? (1) : (0); 426 | 427 | template static uint8_t getNPciByte (CFWrapper const &f) { return f.get (N_PCI_OFSET); } 428 | template static IsoNPduType getType (CFWrapper const &f) { return IsoNPduType ((getNPciByte (f) & 0xF0) >> 4); } 429 | 430 | /*---------------------------------------------------------------------------*/ 431 | 432 | template static uint8_t getDataLengthS (CFWrapper const &f) { return getNPciByte (f) & 0x0f; } 433 | template static uint16_t getDataLengthF (CFWrapper const &f) 434 | { 435 | return ((getNPciByte (f) & 0x0f) << 8) | f.get (N_PCI_OFSET + 1); 436 | } 437 | template static uint8_t getSerialNumber (CFWrapper const &f) { return getNPciByte (f) & 0x0f; } 438 | template static FlowStatus getFlowStatus (CFWrapper const &f) { return FlowStatus (getNPciByte (f) & 0x0f); } 439 | }; 440 | 441 | template struct AddressTraits : public AddressTraitsBase> { 442 | static constexpr bool USING_EXTENDED = false; 443 | }; 444 | 445 | template <> struct AddressTraits : public AddressTraitsBase> { 446 | static constexpr bool USING_EXTENDED = true; 447 | }; 448 | 449 | template <> struct AddressTraits : public AddressTraitsBase> { 450 | static constexpr bool USING_EXTENDED = true; 451 | }; 452 | 453 | template <> struct AddressTraits : public AddressTraitsBase> { 454 | static constexpr bool USING_EXTENDED = true; 455 | }; 456 | 457 | template <> struct AddressTraits : public AddressTraitsBase> { 458 | static constexpr bool USING_EXTENDED = true; 459 | }; 460 | 461 | } // namespace tp 462 | -------------------------------------------------------------------------------- /test/unit-test/05AddressTest.cc: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #include "LinuxTransportProtocol.h" 10 | #include 11 | 12 | 13 | using namespace tp; 14 | 15 | TEST_CASE ("Normal11 encode", "[address]") 16 | { 17 | { 18 | // Simple address 19 | CanFrameWrapper cfw{CanFrame ()}; 20 | REQUIRE (Normal11AddressEncoder::toFrame (Address (0x12, 0x34), cfw)); 21 | REQUIRE (cfw.getId () == 0x34); 22 | REQUIRE (!cfw.isExtended ()); 23 | } 24 | 25 | { 26 | // Maximu value that can be stored into STD CAN frame id 27 | CanFrameWrapper cfw{CanFrame ()}; 28 | REQUIRE (Normal11AddressEncoder::toFrame (Address (0x7fe, 0x7ff), cfw)); 29 | REQUIRE (cfw.getId () == 0x7ff); 30 | REQUIRE (!cfw.isExtended ()); 31 | } 32 | 33 | { 34 | // Value is too big to be stored, thus error has to be risen. 35 | CanFrameWrapper cfw{CanFrame ()}; 36 | REQUIRE (!Normal11AddressEncoder::toFrame (Address (0x00, 0x800), cfw)); 37 | } 38 | } 39 | 40 | TEST_CASE ("Normal11 decode", "[address]") 41 | { 42 | { 43 | // Simple address 44 | CanFrameWrapper cfw{CanFrame ()}; 45 | cfw.setId (0x12); 46 | cfw.setExtended (false); 47 | auto a = Normal11AddressEncoder::fromFrame (cfw); 48 | REQUIRE (a); 49 | REQUIRE (a->getTxId () == 0x12); 50 | } 51 | 52 | { 53 | // Big numer address (max tha can fit) 54 | CanFrameWrapper cfw{CanFrame ()}; 55 | cfw.setId (0x7ff); 56 | cfw.setExtended (false); 57 | auto a = Normal11AddressEncoder::fromFrame (cfw); 58 | REQUIRE (a); 59 | REQUIRE (a->getTxId () == 0x7ff); 60 | } 61 | 62 | { 63 | // Too big value stored in the CAN ID somehow (it shouln'd be since STD ID is only 11 bits long). 64 | CanFrameWrapper cfw{CanFrame ()}; 65 | cfw.setId (0x800); 66 | cfw.setExtended (false); 67 | auto a = Normal11AddressEncoder::fromFrame (cfw); 68 | REQUIRE (!a); 69 | } 70 | 71 | { 72 | // Somebody tried to decode use 11 bit decoder on 29 bit frame -> fail. 73 | CanFrameWrapper cfw{CanFrame ()}; 74 | cfw.setId (0x12); 75 | cfw.setExtended (true); 76 | auto a = Normal11AddressEncoder::fromFrame (cfw); 77 | REQUIRE (!a); 78 | } 79 | } 80 | 81 | TEST_CASE ("Normal29 encode", "[address]") 82 | { 83 | { 84 | // Simple address 85 | CanFrameWrapper cfw{CanFrame ()}; 86 | REQUIRE (Normal29AddressEncoder::toFrame (Address (0x12, 0x34), cfw)); 87 | REQUIRE (cfw.getId () == 0x34); 88 | REQUIRE (cfw.isExtended ()); 89 | } 90 | 91 | { 92 | // Maximu value that can be stored into STD CAN frame id 93 | CanFrameWrapper cfw{CanFrame ()}; 94 | REQUIRE (Normal29AddressEncoder::toFrame (Address (0x1FFFFFFe, 0x1FFFFFFF), cfw)); 95 | REQUIRE (cfw.getId () == 0x1FFFFFFF); 96 | REQUIRE (cfw.isExtended ()); 97 | } 98 | 99 | { 100 | // Value is too big to be stored, thus error has to be risen. 101 | CanFrameWrapper cfw{CanFrame ()}; 102 | REQUIRE (!Normal29AddressEncoder::toFrame (Address (0x00, 0x20000000), cfw)); 103 | } 104 | } 105 | 106 | TEST_CASE ("Normal29 decode", "[address]") 107 | { 108 | { 109 | // Simple address 110 | CanFrameWrapper cfw{CanFrame ()}; 111 | cfw.setId (0x12); 112 | cfw.setExtended (true); 113 | auto a = Normal29AddressEncoder::fromFrame (cfw); 114 | REQUIRE (a); 115 | REQUIRE (a->getTxId () == 0x12); 116 | } 117 | 118 | { 119 | // Big numer address (max tha can fit) 120 | CanFrameWrapper cfw{CanFrame ()}; 121 | cfw.setId (0x1FFFFFFF); 122 | cfw.setExtended (true); 123 | auto a = Normal29AddressEncoder::fromFrame (cfw); 124 | REQUIRE (a); 125 | REQUIRE (a->getTxId () == 0x1FFFFFFF); 126 | } 127 | 128 | { 129 | // Too big value stored in the CAN ID somehow (it shouln'd be since STD ID is only 11 bits long). 130 | CanFrameWrapper cfw{CanFrame ()}; 131 | cfw.setId (0x20000000); 132 | cfw.setExtended (true); 133 | auto a = Normal29AddressEncoder::fromFrame (cfw); 134 | REQUIRE (!a); 135 | } 136 | 137 | { 138 | // Somebody tried to decode use 11 bit decoder on 29 bit frame -> fail. 139 | CanFrameWrapper cfw{CanFrame ()}; 140 | cfw.setId (0x12); 141 | cfw.setExtended (false); 142 | auto a = Normal29AddressEncoder::fromFrame (cfw); 143 | REQUIRE (!a); 144 | } 145 | } 146 | 147 | TEST_CASE ("NormalFixed29 encode", "[address]") 148 | { 149 | { 150 | // Simple address 151 | CanFrameWrapper cfw{CanFrame ()}; 152 | REQUIRE (NormalFixed29AddressEncoder::toFrame ( 153 | Address (0, 0, 0x12, 0x34, Address::MessageType::DIAGNOSTICS, Address::TargetAddressType::PHYSICAL), cfw)); 154 | 155 | REQUIRE (cfw.getId () == ((0b110 << 26) | (218 << 16) | (0x34 << 8) | 0x12)); 156 | REQUIRE (cfw.isExtended ()); 157 | } 158 | 159 | { 160 | // Max 161 | CanFrameWrapper cfw{CanFrame ()}; 162 | REQUIRE (NormalFixed29AddressEncoder::toFrame ( 163 | Address (0, 0, 0xfe, 0xff, Address::MessageType::DIAGNOSTICS, Address::TargetAddressType::PHYSICAL), cfw)); 164 | 165 | REQUIRE (cfw.getId () == ((0b110 << 26) | (218 << 16) | (0xff << 8) | 0xfe)); 166 | REQUIRE (cfw.isExtended ()); 167 | } 168 | } 169 | 170 | TEST_CASE ("NormalFixed29 decode", "[address]") 171 | { 172 | { 173 | // Simple address (Physical) 174 | CanFrameWrapper cfw{CanFrame ()}; 175 | cfw.setId ((0b110 << 26) | (218 << 16) | (0x34 << 8) | 0x12); 176 | cfw.setExtended (true); 177 | auto a = NormalFixed29AddressEncoder::fromFrame (cfw); 178 | REQUIRE (a); 179 | REQUIRE (a->getSourceAddress () == 0x12); 180 | REQUIRE (a->getTargetAddress () == 0x34); 181 | REQUIRE (a->getTargetAddressType () == Address::TargetAddressType::PHYSICAL); 182 | } 183 | 184 | { 185 | // Simple address (Functional) 186 | CanFrameWrapper cfw{CanFrame ()}; 187 | cfw.setId ((0b110 << 26) | (219 << 16) | (0x34 << 8) | 0x12); 188 | cfw.setExtended (true); 189 | auto a = NormalFixed29AddressEncoder::fromFrame (cfw); 190 | REQUIRE (a); 191 | REQUIRE (a->getSourceAddress () == 0x12); 192 | REQUIRE (a->getTargetAddress () == 0x34); 193 | REQUIRE (a->getTargetAddressType () == Address::TargetAddressType::FUNCTIONAL); 194 | } 195 | 196 | { 197 | // Max 198 | CanFrameWrapper cfw{CanFrame ()}; 199 | cfw.setId ((0b110 << 26) | (218 << 16) | (0xff << 8) | 0xfe); 200 | cfw.setExtended (true); 201 | auto a = NormalFixed29AddressEncoder::fromFrame (cfw); 202 | REQUIRE (a); 203 | REQUIRE (a->getSourceAddress () == 0xfe); 204 | REQUIRE (a->getTargetAddress () == 0xff); 205 | REQUIRE (a->getTargetAddressType () == Address::TargetAddressType::PHYSICAL); 206 | } 207 | 208 | { 209 | // malformed 210 | CanFrameWrapper cfw{CanFrame ()}; 211 | cfw.setId ((0b100 << 26) | (218 << 16) | (0xff << 8) | 0xfe); 212 | cfw.setExtended (true); 213 | REQUIRE (!NormalFixed29AddressEncoder::fromFrame (cfw)); 214 | } 215 | 216 | { 217 | // malformed 2 218 | CanFrameWrapper cfw{CanFrame ()}; 219 | cfw.setId ((0b110 << 26) | (228 << 16) | (0xff << 8) | 0xfe); 220 | cfw.setExtended (true); 221 | REQUIRE (!NormalFixed29AddressEncoder::fromFrame (cfw)); 222 | } 223 | 224 | { 225 | // malformed 3 226 | CanFrameWrapper cfw{CanFrame ()}; 227 | cfw.setId (0xffffffff); 228 | cfw.setExtended (true); 229 | REQUIRE (!NormalFixed29AddressEncoder::fromFrame (cfw)); 230 | } 231 | 232 | { 233 | // 11 instead of 29 234 | CanFrameWrapper cfw{CanFrame ()}; 235 | cfw.setId ((0b1010 << 26) | (218 << 16) | (0xff << 8) | 0xfe); 236 | cfw.setExtended (false); 237 | REQUIRE (!NormalFixed29AddressEncoder::fromFrame (cfw)); 238 | } 239 | } 240 | 241 | TEST_CASE ("Extended11bits encode", "[address]") 242 | { 243 | { 244 | // Simple address 245 | CanFrameWrapper cfw{CanFrame ()}; 246 | REQUIRE (Extended11AddressEncoder::toFrame (Address (0x123, 0x456, 0x55, 0xaa), cfw)); 247 | 248 | REQUIRE (cfw.getId () == 0x456); 249 | REQUIRE (cfw.getDlc () == 1); 250 | REQUIRE (cfw.get (0) == 0xaa); 251 | REQUIRE (!cfw.isExtended ()); 252 | } 253 | 254 | { 255 | // Too big value 256 | CanFrameWrapper cfw{CanFrame ()}; 257 | REQUIRE (!Extended11AddressEncoder::toFrame (Address (0x1123, 0x2456, 0x55, 0xaa), cfw)); 258 | } 259 | } 260 | 261 | TEST_CASE ("Extended11bits decode", "[address]") 262 | { 263 | { 264 | // Simple address 265 | CanFrameWrapper cfw{CanFrame ()}; 266 | cfw.setId (0x456); 267 | cfw.setExtended (false); 268 | cfw.setDlc (1); 269 | cfw.set (0, 0xaa); 270 | auto a = Extended11AddressEncoder::fromFrame (cfw); 271 | REQUIRE (a); 272 | REQUIRE (a->getTxId () == 0x456); 273 | REQUIRE (a->getTargetAddress () == 0xaa); 274 | REQUIRE (a->getTargetAddressType () == Address::TargetAddressType::PHYSICAL); 275 | } 276 | 277 | { 278 | // Too big value in ID 279 | CanFrameWrapper cfw{CanFrame ()}; 280 | cfw.setId (0x1456); 281 | cfw.setExtended (false); 282 | cfw.setDlc (1); 283 | cfw.set (0, 0xaa); 284 | auto a = Extended11AddressEncoder::fromFrame (cfw); 285 | REQUIRE (!a); 286 | } 287 | 288 | { 289 | // Extended frame instead of STD 290 | CanFrameWrapper cfw{CanFrame ()}; 291 | cfw.setId (0x456); 292 | cfw.setExtended (true); 293 | cfw.setDlc (1); 294 | cfw.set (0, 0xaa); 295 | auto a = Extended11AddressEncoder::fromFrame (cfw); 296 | REQUIRE (!a); 297 | } 298 | 299 | { 300 | // No data 301 | CanFrameWrapper cfw{CanFrame ()}; 302 | cfw.setId (0x456); 303 | cfw.setExtended (false); 304 | cfw.setDlc (0); 305 | auto a = Extended11AddressEncoder::fromFrame (cfw); 306 | REQUIRE (!a); 307 | } 308 | } 309 | 310 | TEST_CASE ("Extended29bits encode", "[address]") 311 | { 312 | { 313 | // Simple address 314 | CanFrameWrapper cfw{CanFrame ()}; 315 | REQUIRE (Extended29AddressEncoder::toFrame (Address (0x123456, 0x789abc, 0x55, 0xaa), cfw)); 316 | 317 | REQUIRE (cfw.getId () == 0x789abc); 318 | REQUIRE (cfw.getDlc () == 1); 319 | REQUIRE (cfw.get (0) == 0xaa); 320 | REQUIRE (cfw.isExtended ()); 321 | } 322 | 323 | { 324 | // Too big value 325 | CanFrameWrapper cfw{CanFrame ()}; 326 | REQUIRE (!Extended29AddressEncoder::toFrame (Address (0xffffffff, 0xffffffff, 0x55, 0xaa), cfw)); 327 | } 328 | } 329 | 330 | TEST_CASE ("Extended29bits decode", "[address]") 331 | { 332 | { 333 | // Simple address 334 | CanFrameWrapper cfw{CanFrame ()}; 335 | cfw.setId (0x789abc); 336 | cfw.setExtended (true); 337 | cfw.setDlc (1); 338 | cfw.set (0, 0xaa); 339 | auto a = Extended29AddressEncoder::fromFrame (cfw); 340 | REQUIRE (a); 341 | REQUIRE (a->getTxId () == 0x789abc); 342 | REQUIRE (a->getTargetAddress () == 0xaa); 343 | } 344 | 345 | { 346 | // Too big value in ID 347 | CanFrameWrapper cfw{CanFrame ()}; 348 | cfw.setId (0xffffffff); 349 | cfw.setExtended (true); 350 | cfw.setDlc (1); 351 | cfw.set (0, 0xaa); 352 | auto a = Extended29AddressEncoder::fromFrame (cfw); 353 | REQUIRE (!a); 354 | } 355 | 356 | { 357 | // STD frame instead of ext 358 | CanFrameWrapper cfw{CanFrame ()}; 359 | cfw.setId (0x789abc); 360 | cfw.setExtended (false); 361 | cfw.setDlc (1); 362 | cfw.set (0, 0xaa); 363 | auto a = Extended29AddressEncoder::fromFrame (cfw); 364 | REQUIRE (!a); 365 | } 366 | 367 | { 368 | // No data 369 | CanFrameWrapper cfw{CanFrame ()}; 370 | cfw.setId (0x789abc); 371 | cfw.setExtended (true); 372 | cfw.setDlc (0); 373 | auto a = Extended29AddressEncoder::fromFrame (cfw); 374 | REQUIRE (!a); 375 | } 376 | } 377 | 378 | TEST_CASE ("Mixed11bits encode", "[address]") 379 | { 380 | { 381 | // Simple address 382 | CanFrameWrapper cfw{CanFrame ()}; 383 | REQUIRE (Mixed11AddressEncoder::toFrame (Address (0x123, 0x456, 0, 0, 0xaa), cfw)); 384 | 385 | REQUIRE (cfw.getId () == 0x456); 386 | REQUIRE (cfw.getDlc () == 1); 387 | REQUIRE (cfw.get (0) == 0xaa); 388 | REQUIRE (!cfw.isExtended ()); 389 | } 390 | 391 | { 392 | // Too big value 393 | CanFrameWrapper cfw{CanFrame ()}; 394 | REQUIRE (!Mixed11AddressEncoder::toFrame (Address (0xffffffff, 0xffffffff, 0, 0, 0xaa), cfw)); 395 | } 396 | } 397 | 398 | TEST_CASE ("Mixed11bits decode", "[address]") 399 | { 400 | { 401 | // Simple address 402 | CanFrameWrapper cfw{CanFrame ()}; 403 | cfw.setId (0x456); 404 | cfw.setExtended (false); 405 | cfw.setDlc (1); 406 | cfw.set (0, 0xaa); 407 | auto a = Mixed11AddressEncoder::fromFrame (cfw); 408 | REQUIRE (a); 409 | REQUIRE (a->getTxId () == 0x456); 410 | REQUIRE (a->getNetworkAddressExtension () == 0xaa); 411 | REQUIRE (a->getTargetAddressType () == Address::TargetAddressType::PHYSICAL); 412 | } 413 | 414 | { 415 | // Too big value in ID 416 | CanFrameWrapper cfw{CanFrame ()}; 417 | cfw.setId (0x1456); 418 | cfw.setExtended (false); 419 | cfw.setDlc (1); 420 | cfw.set (0, 0xaa); 421 | auto a = Mixed11AddressEncoder::fromFrame (cfw); 422 | REQUIRE (!a); 423 | } 424 | 425 | { 426 | // Extended frame instead of STD 427 | CanFrameWrapper cfw{CanFrame ()}; 428 | cfw.setId (0x456); 429 | cfw.setExtended (true); 430 | cfw.setDlc (1); 431 | cfw.set (0, 0xaa); 432 | auto a = Mixed11AddressEncoder::fromFrame (cfw); 433 | REQUIRE (!a); 434 | } 435 | 436 | { 437 | // No data 438 | CanFrameWrapper cfw{CanFrame ()}; 439 | cfw.setId (0x456); 440 | cfw.setExtended (false); 441 | cfw.setDlc (0); 442 | auto a = Mixed11AddressEncoder::fromFrame (cfw); 443 | REQUIRE (!a); 444 | } 445 | } 446 | 447 | TEST_CASE ("Mixed29bits encode", "[address]") 448 | { 449 | { 450 | // Simple address 451 | CanFrameWrapper cfw{CanFrame ()}; 452 | REQUIRE (Mixed29AddressEncoder::toFrame ( 453 | Address (0, 0, 0x55, 0xaa, 0x77, tp::Address::MessageType::REMOTE_DIAGNOSTICS, tp::Address::TargetAddressType::PHYSICAL), 454 | cfw)); 455 | 456 | REQUIRE (cfw.getId () == ((0b110 << 26) | (206 << 16) | (0xaa << 8) | 0x55)); 457 | REQUIRE (cfw.getDlc () == 1); 458 | REQUIRE (cfw.get (0) == 0x77); 459 | REQUIRE (cfw.isExtended ()); 460 | } 461 | 462 | { 463 | // Simple address 464 | CanFrameWrapper cfw{CanFrame ()}; 465 | REQUIRE (Mixed29AddressEncoder::toFrame (Address (0, 0, 0x55, 0xaa, 0x77, tp::Address::MessageType::REMOTE_DIAGNOSTICS, 466 | tp::Address::TargetAddressType::FUNCTIONAL), 467 | cfw)); 468 | 469 | REQUIRE (cfw.getId () == ((0b110 << 26) | (205 << 16) | (0xaa << 8) | 0x55)); 470 | REQUIRE (cfw.getDlc () == 1); 471 | REQUIRE (cfw.get (0) == 0x77); 472 | REQUIRE (cfw.isExtended ()); 473 | } 474 | } 475 | 476 | TEST_CASE ("Mixed29bits decode", "[address]") 477 | { 478 | { 479 | // Simple address (physical)_ 480 | CanFrameWrapper cfw{CanFrame ()}; 481 | cfw.setId (((0b110 << 26) | (206 << 16) | (0xaa << 8) | 0x55)); 482 | cfw.setExtended (true); 483 | cfw.setDlc (1); 484 | cfw.set (0, 0x77); 485 | auto a = Mixed29AddressEncoder::fromFrame (cfw); 486 | REQUIRE (a); 487 | REQUIRE (a->getSourceAddress () == 0x55); 488 | REQUIRE (a->getTargetAddress () == 0xaa); 489 | REQUIRE (a->getNetworkAddressExtension () == 0x77); 490 | REQUIRE (a->getMessageType () == tp::Address::MessageType::REMOTE_DIAGNOSTICS); 491 | REQUIRE (a->getTargetAddressType () == tp::Address::TargetAddressType::PHYSICAL); 492 | } 493 | 494 | { 495 | // Simple address (functional)_ 496 | CanFrameWrapper cfw{CanFrame ()}; 497 | cfw.setId (((0b110 << 26) | (205 << 16) | (0xaa << 8) | 0x55)); 498 | cfw.setExtended (true); 499 | cfw.setDlc (1); 500 | cfw.set (0, 0x77); 501 | auto a = Mixed29AddressEncoder::fromFrame (cfw); 502 | REQUIRE (a); 503 | REQUIRE (a->getSourceAddress () == 0x55); 504 | REQUIRE (a->getTargetAddress () == 0xaa); 505 | REQUIRE (a->getNetworkAddressExtension () == 0x77); 506 | REQUIRE (a->getMessageType () == tp::Address::MessageType::REMOTE_DIAGNOSTICS); 507 | REQUIRE (a->getTargetAddressType () == tp::Address::TargetAddressType::FUNCTIONAL); 508 | } 509 | 510 | { 511 | // Malformwed ID 512 | CanFrameWrapper cfw{CanFrame ()}; 513 | cfw.setId (0xffffffff); 514 | cfw.setExtended (true); 515 | cfw.setDlc (1); 516 | cfw.set (0, 0xaa); 517 | auto a = Mixed29AddressEncoder::fromFrame (cfw); 518 | REQUIRE (!a); 519 | } 520 | 521 | { 522 | // STD frame instead of ext 523 | CanFrameWrapper cfw{CanFrame ()}; 524 | cfw.setId (((0b110 << 26) | (205 << 16) | (0xaa << 8) | 0x55)); 525 | cfw.setExtended (false); 526 | cfw.setDlc (1); 527 | cfw.set (0, 0xaa); 528 | auto a = Mixed29AddressEncoder::fromFrame (cfw); 529 | REQUIRE (!a); 530 | } 531 | 532 | { 533 | // No data 534 | CanFrameWrapper cfw{CanFrame ()}; 535 | cfw.setId (((0b110 << 26) | (205 << 16) | (0xaa << 8) | 0x55)); 536 | cfw.setExtended (true); 537 | cfw.setDlc (0); 538 | auto a = Mixed29AddressEncoder::fromFrame (cfw); 539 | REQUIRE (!a); 540 | } 541 | } 542 | -------------------------------------------------------------------------------- /src/TransportProtocol.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | * * 3 | * Author : lukasz.iwaszkiewicz@gmail.com * 4 | * ~~~~~~~~ * 5 | * License : see COPYING file for details. * 6 | * ~~~~~~~~~ * 7 | ****************************************************************************/ 8 | 9 | #pragma once 10 | #include "Address.h" 11 | #include "CanFrame.h" 12 | #include "CppCompat.h" 13 | #include "MiscTypes.h" 14 | 15 | /** 16 | * Set maximum number of Flow Control frames with WAIT bit set that can be received 17 | * before aborting further transmission of pending message. Check paragraph 6.6 of ISO 18 | * on page 23. 19 | */ 20 | #if !defined(MAX_WAIT_FRAME_NUMBER) 21 | #define MAX_WAIT_FRAME_NUMBER 10 22 | #endif 23 | 24 | namespace tp { 25 | 26 | /** 27 | * 28 | */ 29 | template 31 | struct TransportProtocolTraits { 32 | using CanFrame = CanFrameT; 33 | using IsoMessageTT = IsoMessageT; 34 | using CanOutputInterface = CanOutputInterfaceT; 35 | using TimeProvider = TimeProviderT; 36 | using ErrorHandler = ExceptionHandlerT; 37 | using Callback = CallbackT; 38 | using AddressEncoderT = AddressResolverT; 39 | static constexpr size_t MAX_MESSAGE_SIZE = MAX_MESSAGE_SIZE_N; 40 | static constexpr size_t MAX_INTERLEAVED_ISO_MESSAGES = MAX_INTERLEAVED_ISO_MESSAGES_N; 41 | }; 42 | 43 | /* 44 | * As in 6.7.1 "Timing parameters". According to ISO 15765-2 it's 1000ms. 45 | * ISO 15765-4 and J1979 applies further restrictions down to 50ms but it applies to 46 | * time between query and the response of tester messages. See J1979:2006 chapter 5.2.2.6 47 | */ 48 | static constexpr size_t N_A_TIMEOUT = 1500; 49 | static constexpr size_t N_BS_TIMEOUT = 1500; 50 | static constexpr size_t N_CR_TIMEOUT = 1500; 51 | 52 | /// Max allowed by the ISO standard. 53 | static constexpr int MAX_ALLOWED_ISO_MESSAGE_SIZE = 4095; 54 | 55 | /** 56 | * Implements ISO 15765-2 which is also called CAN ISO-TP or CAN ISO transport protocol. 57 | * Sources: 58 | * - ISO-15765-2-2004.pdf document. 59 | * - https://en.wikipedia.org/wiki/ISO_15765-2 60 | * - http://canbushack.com/iso-15765-2/ 61 | * - http://iwasz.pl/private/index.php/STM32_CAN#ISO_15765-2 62 | * 63 | * Note : whenever I refer to a paragraph, or a page in an ISO document, I mean the ISO 15765-2:2004 64 | * because this is the only one I could find for free. 65 | * 66 | * Note : I'm assuming full-duplex communication, when onCanNewFrame and run calls are 67 | * interleaved. 68 | */ 69 | template class TransportProtocol { 70 | public: 71 | using CanFrame = typename TraitsT::CanFrame; 72 | using IsoMessageT = typename TraitsT::IsoMessageTT; 73 | using CanOutputInterface = typename TraitsT::CanOutputInterface; 74 | using TimeProvider = typename TraitsT::TimeProvider; 75 | using ErrorHandler = typename TraitsT::ErrorHandler; 76 | using Callback = typename TraitsT::Callback; 77 | using CanFrameWrapperType = CanFrameWrapper; 78 | using AddressEncoderT = typename TraitsT::AddressEncoderT; 79 | using AddressTraitsT = AddressTraits; 80 | 81 | static constexpr size_t MAX_INTERLEAVED_ISO_MESSAGES = TraitsT::MAX_INTERLEAVED_ISO_MESSAGES; 82 | 83 | /// Max allowed by this implementation. Can be lowered if memory is scarce. 84 | static constexpr size_t MAX_ACCEPTED_ISO_MESSAGE_SIZE = TraitsT::MAX_MESSAGE_SIZE; 85 | 86 | TransportProtocol (Callback callback, CanOutputInterface outputInterface = {}, TimeProvider /*timeProvider*/ = {}, 87 | ErrorHandler errorHandler = {}) 88 | : callback{callback}, 89 | outputInterface{outputInterface}, 90 | // timeProvider{ timeProvider }, 91 | errorHandler{errorHandler}, 92 | stateMachine{this->outputInterface} 93 | { 94 | } 95 | 96 | TransportProtocol (Address myAddress, Callback callback, CanOutputInterface outputInterface = {}, TimeProvider /*timeProvider*/ = {}, 97 | ErrorHandler errorHandler = {}) 98 | : callback{callback}, 99 | outputInterface{outputInterface}, 100 | // timeProvider{ timeProvider }, 101 | errorHandler{errorHandler}, 102 | stateMachine{*this, this->outputInterface}, 103 | myAddress (myAddress) 104 | { 105 | } 106 | 107 | // TODO Consider whether to implement copy / move semantics or not. 108 | TransportProtocol (TransportProtocol const &) = delete; 109 | TransportProtocol (TransportProtocol &&) = delete; 110 | TransportProtocol &operator= (TransportProtocol const &) = delete; 111 | TransportProtocol &operator= (TransportProtocol &&) = delete; 112 | ~TransportProtocol () = default; 113 | 114 | /** 115 | * Sends a message. If msg is so long, that it wouldn't fit in a SINGLE_FRAME (6 or 7 bytes 116 | * depending on addressing used), it is COPIED into the transport protocol object for further 117 | * processing, and then via run method is send in multiple CONSECUTIVE_FRAMES. 118 | * In ISO this method is called a 'request' 119 | */ 120 | // template bool send (Address const &a, IsoMessageSup &&msg); 121 | bool send (Address const &a, IsoMessageT &&msg); 122 | bool send (Address const &a, IsoMessageT const &msg); 123 | 124 | /** 125 | * Sends a message. If msg is so long, that it wouldn't fit in a SINGLE_FRAME (6 or 7 bytes 126 | * depending on addressing used), it is MOVED into the transport protocol object for further 127 | * processing, and then via run method is send in multiple CONSECUTIVE_FRAMES. 128 | * In ISO this method is called a 'request' 129 | */ 130 | template bool send (IsoMessageSup &&msg) 131 | { 132 | return send (myAddress, std::forward (msg)); 133 | } 134 | 135 | /** 136 | * Does the book keeping (checks for timeouts, runs the sending state machine). 137 | */ 138 | void run (); 139 | 140 | /** 141 | * 142 | */ 143 | bool isSending () const { return stateMachine.getState () != StateMachine::State::DONE; } 144 | 145 | /* 146 | * API jest asynchroniczne, bo na prawdę nie ma tego jak inaczej zrobić. Ramki CAN 147 | * przychodzą asynchronicznie (odpowiedzi na żądanie, ale także mogą przyjść same z 148 | * siebie) oraz może ich przyjść różna ilość. Zadając jakieś pytanie (na przykład o 149 | * błędy) nie jesteśmy w stanie do końca powiedzieć ile tych ramek przyjdzie. Na 150 | * przykład jeżeli mamy 1 ECU, to mogą przyjść 2 ramki składające się na 1 wiadomość 151 | * ISO, ale jeżeli mamy 2 ECU to mogą przyjść 3 albo 4 i tak dalej. 152 | * 153 | * API synchroniczne działało tak, że oczekiwało odpowiedzi tylko od jednego ECU. To 154 | * może być SingleFrame, albo FF + x * CF, ale dało się określić koniec tej wiadomości. 155 | * Kiedy wykryło koniec, to kończyło działanie ignorując ewentualne inne wiadomości. 156 | * Asynchroniczne API naprawia ten problem. 157 | * 158 | * Składa wiadomość ISO z poszczególnych ramek CAN. Zwraca czy potrzeba więcej ramek can aby 159 | * złożyć wiadomość ISO w całości. Kiedy podamy obiekt wiadomości jako drugi parametr, to 160 | * nie wywołuje callbacku, tylko zapisuje wynik w tym obiekcie. To jest używane w connect. 161 | */ 162 | bool onCanNewFrame (CanFrame const &f) { return onCanNewFrame (CanFrameWrapperType{f}); } 163 | 164 | /** 165 | * myAddress address is used during reception 166 | * - target address of incoming message is checked with myAddress.sourceAddress 167 | * - flow control frames during reception are sent with myAddress.targetAddress. 168 | * And during sending: 169 | * - myAddress.targetAddress is used for outgoing frames if no address was specified during request (in send method). 170 | * - myAddress.sourceAddress is checked with incoming flowFrames if no address was specified during request (in send method). 171 | */ 172 | void setMyAddress (Address const &a) { myAddress = a; } 173 | Address const &getMyAddress () const { return myAddress; } 174 | 175 | /** 176 | * This value will be sent in first flow control flow frame, and it tells the peer 177 | * how long to wait between sending consecutive frames. This is to offload the receiver. 178 | * Paragraph 6.5.5.5 in the 2004 ISO document. Default value 0 means that everything 179 | * has to be sent immediately. Values are: 180 | * 181 | * - 0x00 : 0x7f -> 0 : 127 ms. 182 | * - 0xf1 : 0xf9 -> 100 : 900 µs. 183 | * 184 | * Other values are reserved. 185 | */ 186 | void setSeparationTime (uint8_t s) 187 | { 188 | Expects ((s >= 0 && s <= 0x7f) || (s >= 0xf1 && s <= 0xf9)); 189 | separationTime = s; 190 | } 191 | 192 | /** 193 | * This value will be sent in first flow control flow frame, and it tells the peer 194 | * how many consecutive frames to send in one burst. Then the receiver (the peer 195 | * who calls setBlockSize) has to send a flow frame acknowledging received frames and 196 | * and thus allowing for next batch of consecutive frames. Default 0 means that the 197 | * receiver wants all frames at once without any interruption. See 6.5.5.4. 198 | */ 199 | void setBlockSize (uint8_t b) { blockSize = b; } 200 | 201 | #ifndef UNIT_TEST 202 | private: 203 | #endif 204 | 205 | static uint32_t now () 206 | { 207 | static TimeProvider tp; 208 | return tp (); 209 | } 210 | 211 | /* 212 | * @brief The Timer class 213 | * TODO this timer should have 100µs resolution and 100µs units. 214 | */ 215 | class Timer { 216 | public: 217 | Timer (uint32_t intervalMs = 0) { start (intervalMs); } 218 | 219 | /// Resets the timer (it starts from 0) and sets the interval. So isExpired will return true after whole interval has passed. 220 | void start (uint32_t intervalMs) 221 | { 222 | this->intervalMs = intervalMs; 223 | this->startTime = getTick (); 224 | } 225 | 226 | /// Change interval without reseting the timer. Can extend as well as shorten. 227 | void extend (uint32_t intervalMs) { this->intervalMs = intervalMs; } 228 | 229 | /// Says if intervalMs has passed since start () was called. 230 | bool isExpired () const { return elapsed () >= intervalMs; } 231 | 232 | /// Returns how many ms has passed since start () was called. 233 | uint32_t elapsed () const 234 | { 235 | uint32_t actualTime = getTick (); 236 | return actualTime - startTime; 237 | } 238 | 239 | /// Convenience method, simple delay ms. 240 | void delay (uint32_t delayMs) 241 | { 242 | Timer t{delayMs}; 243 | while (!t.isExpired ()) { 244 | } 245 | } 246 | 247 | /// Returns system wide ms since system start. 248 | uint32_t getTick () const { return now (); } 249 | 250 | private: 251 | uint32_t startTime = 0; 252 | uint32_t intervalMs = 0; 253 | }; 254 | 255 | /* 256 | * An ISO 15765-2 message (up to 4095B long). Messages composed from CAN frames. 257 | */ 258 | struct TransportMessage { 259 | 260 | int append (CanFrameWrapperType const &frame, size_t offset, size_t len); 261 | 262 | // uint32_t address = 0; /// Address Information M_AI 263 | IsoMessageT data{}; /// Max 4095 (according to ISO 15765-2) or less if more strict requirements programmed by the user. 264 | int multiFrameRemainingLen{}; /// For tracking number of bytes remaining. 265 | int currentSn{}; /// Sequence number of Consecutive Frame. 266 | int consecutiveFramesReceived{}; /// For comparison with block size. 267 | Timer timer; /// For tracking time between first and consecutive frames with the same address. 268 | Result timeoutReason{}; /// It timer expired, what was the result. 269 | }; 270 | 271 | /* 272 | * StateMachine class implements an algorithm for sending a single ISO message, which 273 | * can be up to 4095B long and thus has to be divided into multiple CAN frames. 274 | */ 275 | class StateMachine { 276 | public: 277 | enum class State { 278 | IDLE, 279 | SEND_FIRST_FRAME, 280 | RECEIVE_FIRST_FLOW_CONTROL_FRAME, 281 | SEND_CONSECUTIVE_FRAME, 282 | RECEIVE_BS_FLOW_CONTROL_FRAME, 283 | DONE 284 | }; 285 | 286 | explicit StateMachine (TransportProtocol &tp, CanOutputInterface &o) : tp (tp), outputInterface (o) {} 287 | ~StateMachine () = default; 288 | 289 | StateMachine (StateMachine &&sm) noexcept = delete; 290 | StateMachine &operator= (StateMachine &&sm) = delete; 291 | 292 | StateMachine (StateMachine const &sm) noexcept = delete; 293 | StateMachine &operator= (StateMachine const &sm) noexcept = delete; 294 | 295 | void reset (Address const &a, IsoMessageT &&m) 296 | { 297 | myAddress = a; 298 | message = std::move (m); 299 | state = State::IDLE; 300 | 301 | bytesSent = 0; 302 | blocksSent = 0; 303 | sequenceNumber = 1; 304 | receivedBlockSize = 0; 305 | receivedSeparationTimeUs = 0; 306 | waitFrameNumber = 0; 307 | 308 | separationTimer.start (0); 309 | bsCrTimer.start (0); 310 | } 311 | 312 | Status run (CanFrameWrapperType const *frame = nullptr); 313 | State getState () const { return state; } 314 | 315 | private: 316 | TransportProtocol &tp; 317 | CanOutputInterface &outputInterface; 318 | Address myAddress{}; 319 | IsoMessageT message{}; 320 | State state{State::DONE}; 321 | size_t bytesSent{}; 322 | uint16_t blocksSent{}; 323 | uint8_t sequenceNumber{1}; 324 | uint8_t receivedBlockSize{}; 325 | uint32_t receivedSeparationTimeUs{}; 326 | Timer separationTimer{}; 327 | Timer bsCrTimer{}; 328 | uint8_t waitFrameNumber{}; 329 | }; 330 | 331 | /*---------------------------------------------------------------------------*/ 332 | 333 | bool onCanNewFrame (CanFrameWrapperType const &frame); 334 | 335 | /*---------------------------------------------------------------------------*/ 336 | 337 | /// Checks if callback accepts single IsoMessage param thus has the form callback (IsoMessage msg) TODO use std::is_invocable_v 338 | template struct IsCallbackSimple : public etl::false_type { 339 | }; 340 | 341 | /// Checks if callback accepts single IsoMessage param thus has the form callback (IsoMessage msg) 342 | template 343 | struct IsCallbackSimple () (IsoMessageT{})))>::type> 344 | : public etl::true_type { 345 | }; 346 | 347 | /// Checks for a callback which have 3 params like this : Address{}, IsoMessageT{}, Result{} 348 | template struct IsCallbackAdvanced : public etl::false_type { 349 | }; 350 | 351 | /// Checks for a callback which have 3 params like this : Address{}, IsoMessageT{}, Result{} 352 | template 353 | struct IsCallbackAdvanced< 354 | T, typename etl::enable_if () (Address{}, IsoMessageT{}, Result{})))>::type> 355 | : public etl::true_type { 356 | }; 357 | 358 | template struct IsCallbackAdvancedMethod : public etl::false_type { 359 | }; 360 | 361 | template 362 | struct IsCallbackAdvancedMethod< 363 | T, typename etl::enable_if ().indication (Address{}, IsoMessageT{}, Result{})))>::type> 364 | : public etl::true_type { 365 | }; 366 | 367 | template struct HasCallbackConfirmMethod : public etl::false_type { 368 | }; 369 | 370 | template 371 | struct HasCallbackConfirmMethod< 372 | T, typename etl::enable_if ().confirm (Address{}, Result{})))>::type> 373 | : public etl::true_type { 374 | }; 375 | 376 | template struct HasCallbackFFIMethod : public etl::false_type { 377 | }; 378 | 379 | template 380 | struct HasCallbackFFIMethod< 381 | T, typename etl::enable_if ().firstFrameIndication (Address{}, uint16_t{})))>::type> 382 | : public etl::true_type { 383 | }; 384 | 385 | void confirm (Address const &a, Result r) 386 | { 387 | if constexpr (HasCallbackConfirmMethod::value) { 388 | callback.confirm (a, r); 389 | } 390 | } 391 | 392 | void firstFrameIndication (Address const &a, uint16_t len) 393 | { 394 | if constexpr (HasCallbackFFIMethod::value) { 395 | callback.firstFrameIndication (a, len); 396 | } 397 | } 398 | 399 | void indication (Address const &a, IsoMessageT const &msg, Result r) 400 | { 401 | constexpr bool simpleCallback = IsCallbackSimple::value; 402 | constexpr bool advancedCallback = IsCallbackAdvanced::value; 403 | constexpr bool advancedMethodCallback = IsCallbackAdvancedMethod::value; 404 | 405 | static_assert (simpleCallback || advancedCallback || advancedMethodCallback, 406 | "Wrong callback interface. Use either 'simple', 'advanced', or 'advancedMethod' callback. See the README.md for " 407 | "more info."); 408 | 409 | if constexpr (simpleCallback) { 410 | callback (msg); 411 | } 412 | else if constexpr (advancedCallback) { 413 | callback (a, msg, r); 414 | } 415 | else if constexpr (advancedMethodCallback) { 416 | callback.indication (a, msg, r); 417 | } 418 | } 419 | 420 | /*---------------------------------------------------------------------------*/ 421 | 422 | uint32_t getID (bool extended) const; 423 | bool sendFlowFrame (const Address &outgoingAddress, FlowStatus fs = FlowStatus::CONTINUE_TO_SEND); 424 | bool sendSingleFrame (const Address &a, IsoMessageT const &msg); 425 | bool sendMultipleFrames (const Address &a, IsoMessageT &&msg); 426 | 427 | #ifndef UNIT_TEST 428 | private: 429 | #endif 430 | 431 | etl::map transportMessagesMap; 432 | uint8_t blockSize{}; 433 | uint8_t separationTime{}; 434 | Callback callback; 435 | CanOutputInterface outputInterface; 436 | ErrorHandler errorHandler; 437 | StateMachine stateMachine; 438 | Address myAddress; 439 | }; 440 | 441 | /*****************************************************************************/ 442 | 443 | // template template bool TransportProtocol::send (const Address &a, IsoMessageSup &&msg) 444 | // { 445 | // if (msg.size () > MAX_ACCEPTED_ISO_MESSAGE_SIZE) { 446 | // return false; 447 | // } 448 | 449 | // // 6 or 7 depending on addressing used 450 | // const size_t SINGLE_FRAME_MAX_SIZE = 7 - int (AddressTraitsT::USING_EXTENDED); 451 | 452 | // if (msg.size () <= SINGLE_FRAME_MAX_SIZE) { // Send using single Frame 453 | // return sendSingleFrame (a, std::forward (msg)); 454 | // } 455 | 456 | // // Send using multiple frames, state machine, and timing control and whatnot. 457 | // return sendMultipleFrames (a, std::forward (msg)); 458 | // } 459 | 460 | template bool TransportProtocol::send (const Address &a, IsoMessageT &&msg) 461 | { 462 | if (msg.size () > MAX_ACCEPTED_ISO_MESSAGE_SIZE) { 463 | return false; 464 | } 465 | 466 | // 6 or 7 depending on addressing used 467 | const size_t SINGLE_FRAME_MAX_SIZE = 7 - int (AddressTraitsT::USING_EXTENDED); 468 | 469 | if (msg.size () <= SINGLE_FRAME_MAX_SIZE) { // Send using single Frame 470 | return sendSingleFrame (a, msg); 471 | } 472 | 473 | // Send using multiple frames, state machine, and timing control and whatnot. 474 | return sendMultipleFrames (a, std::move (msg)); 475 | } 476 | 477 | template bool TransportProtocol::send (const Address &a, IsoMessageT const &msg) 478 | { 479 | if (msg.size () > MAX_ACCEPTED_ISO_MESSAGE_SIZE) { 480 | return false; 481 | } 482 | 483 | // 6 or 7 depending on addressing used 484 | const size_t SINGLE_FRAME_MAX_SIZE = 7 - int (AddressTraitsT::USING_EXTENDED); 485 | 486 | if (msg.size () <= SINGLE_FRAME_MAX_SIZE) { // Send using single Frame 487 | return sendSingleFrame (a, msg); 488 | } 489 | 490 | // Send using multiple frames, state machine, and timing control and whatnot. 491 | return sendMultipleFrames (a, IsoMessageT (msg)); 492 | } 493 | 494 | /*****************************************************************************/ 495 | 496 | template bool TransportProtocol::sendSingleFrame (const Address &a, IsoMessageT const &msg) 497 | { 498 | CanFrameWrapperType canFrame{0x00, true, (int (IsoNPduType::SINGLE_FRAME) << 4) | (msg.size () & 0x0f)}; 499 | 500 | if (!AddressEncoderT::toFrame (a, canFrame)) { 501 | errorHandler (Status::ADDRESS_ENCODE_ERROR); 502 | return false; 503 | } 504 | 505 | for (size_t i = 0; i < msg.size (); ++i) { 506 | canFrame.set (i + 1, msg.at (i)); 507 | } 508 | 509 | canFrame.setDlc (1 + msg.size ()); 510 | bool result = outputInterface (canFrame.value ()); 511 | 512 | if (!result) { 513 | confirm (myAddress, Result::N_TIMEOUT_A); 514 | } 515 | else { 516 | confirm (myAddress, Result::N_OK); 517 | } 518 | 519 | return result; 520 | } 521 | 522 | /*****************************************************************************/ 523 | 524 | template bool TransportProtocol::sendMultipleFrames (const Address &a, IsoMessageT &&msg) 525 | { 526 | if (stateMachine.getState () != StateMachine::State::DONE) { 527 | return false; 528 | } 529 | 530 | stateMachine.reset (a, std::move (msg)); 531 | return true; 532 | } 533 | 534 | /*****************************************************************************/ 535 | 536 | template bool TransportProtocol::onCanNewFrame (const CanFrameWrapperType &frame) 537 | { 538 | // Address as received in the CAN frame frame. 539 | auto theirAddress = AddressEncoderT::fromFrame (frame); 540 | Address const &outgoingAddress = myAddress; 541 | 542 | // Check if the received frame is meant for us. 543 | if (!theirAddress || !AddressEncoderT::matches (*theirAddress, myAddress)) { 544 | return false; 545 | } 546 | 547 | switch (AddressTraitsT::getType (frame)) { 548 | case IsoNPduType::SINGLE_FRAME: { 549 | TransportMessage message; 550 | int singleFrameLen = AddressTraitsT::getDataLengthS (frame); 551 | 552 | // Error situation. Such frames should be ignored according to 6.5.2.2 page 24. 553 | if (singleFrameLen <= 0 || (AddressTraitsT::USING_EXTENDED && singleFrameLen > 6) || singleFrameLen > 7) { 554 | return false; 555 | } 556 | 557 | auto iter = transportMessagesMap.find (*theirAddress); 558 | 559 | if (iter != transportMessagesMap.cend ()) { // found 560 | // As in 6.7.3 Table 18 561 | indication (*theirAddress, {}, Result::N_UNEXP_PDU); 562 | // Terminate the current reception of segmented message. 563 | transportMessagesMap.erase (iter); 564 | break; 565 | } 566 | 567 | uint8_t dataOffset = AddressTraitsT::N_PCI_OFSET + 1; 568 | message.append (frame, dataOffset, singleFrameLen); 569 | indication (*theirAddress, message.data, Result::N_OK); 570 | } break; 571 | 572 | case IsoNPduType::FIRST_FRAME: { 573 | uint16_t multiFrameRemainingLen = AddressTraitsT::getDataLengthF (frame); 574 | 575 | // Error situation. Such frames should be ignored according to ISO. 576 | if (multiFrameRemainingLen < 8 || (AddressTraitsT::USING_EXTENDED && multiFrameRemainingLen < 7)) { 577 | return false; 578 | } 579 | 580 | // 6.5.3.3 Error situation : too much data. Should reply with appropriate flow control frame. 581 | if (multiFrameRemainingLen > MAX_ACCEPTED_ISO_MESSAGE_SIZE || multiFrameRemainingLen > MAX_ALLOWED_ISO_MESSAGE_SIZE) { 582 | sendFlowFrame (outgoingAddress, FlowStatus::OVERFLOWED); 583 | return false; 584 | } 585 | 586 | // incomingAddress Should be used (as a key)! 587 | auto iter = transportMessagesMap.find (*theirAddress); 588 | 589 | if (iter != transportMessagesMap.cend ()) { 590 | // As in 6.7.3 Table 18 591 | indication (*theirAddress, {}, Result::N_UNEXP_PDU); 592 | // Terminate the current reception of segmented message. 593 | transportMessagesMap.erase (iter); 594 | } 595 | 596 | int firstFrameLen = (AddressTraitsT::USING_EXTENDED) ? (5) : (6); 597 | 598 | if (transportMessagesMap.full ()) { 599 | indication (*theirAddress, {}, Result::N_MESSAGE_NUM_MAX); 600 | return false; 601 | } 602 | 603 | auto &isoMessage = transportMessagesMap[*theirAddress]; 604 | 605 | firstFrameIndication (*theirAddress, multiFrameRemainingLen); 606 | 607 | isoMessage.currentSn = 1; 608 | isoMessage.multiFrameRemainingLen = multiFrameRemainingLen - firstFrameLen; 609 | isoMessage.timer.start (N_BS_TIMEOUT); 610 | isoMessage.timeoutReason = Result::N_TIMEOUT_BS; 611 | uint8_t dataOffset = AddressTraitsT::N_PCI_OFSET + 2; 612 | isoMessage.append (frame, dataOffset, firstFrameLen); 613 | 614 | // Send Flow Control 615 | if (!sendFlowFrame (outgoingAddress, FlowStatus::CONTINUE_TO_SEND)) { 616 | indication (*theirAddress, {}, Result::N_ERROR); 617 | // Terminate the current reception of segmented message. 618 | transportMessagesMap.erase (transportMessagesMap.find (*theirAddress)); 619 | } 620 | 621 | return true; 622 | } break; 623 | 624 | case IsoNPduType::CONSECUTIVE_FRAME: { 625 | auto iter = transportMessagesMap.find (*theirAddress); 626 | 627 | if (iter == transportMessagesMap.cend ()) { 628 | // As in 6.7.3 Table 18 - ignore 629 | return false; 630 | } 631 | 632 | auto &transportMessage = iter->second; 633 | transportMessage.timer.start (N_CR_TIMEOUT); 634 | transportMessage.timeoutReason = Result::N_TIMEOUT_CR; 635 | 636 | if (AddressTraitsT::getSerialNumber (frame) != transportMessage.currentSn) { 637 | // 6.5.4.3 SN error handling 638 | indication (*theirAddress, {}, Result::N_WRONG_SN); 639 | return false; 640 | } 641 | 642 | ++(transportMessage.currentSn); 643 | transportMessage.currentSn %= 16; 644 | int maxConsecutiveFrameLen = (AddressTraitsT::USING_EXTENDED) ? (6) : (7); 645 | int consecutiveFrameLen = std::min (maxConsecutiveFrameLen, transportMessage.multiFrameRemainingLen); 646 | transportMessage.multiFrameRemainingLen -= consecutiveFrameLen; 647 | #if 0 648 | fmt::print ("Bytes left : {}\n", isoMessage->multiFrameRemainingLen); 649 | #endif 650 | 651 | uint8_t dataOffset = AddressTraitsT::N_PCI_OFSET + 1; 652 | transportMessage.append (frame, dataOffset, consecutiveFrameLen); 653 | 654 | // Send flow control frame. 655 | if (blockSize > 0 && ++transportMessage.consecutiveFramesReceived >= blockSize) { 656 | transportMessage.consecutiveFramesReceived = 0; 657 | 658 | if (!sendFlowFrame (outgoingAddress, FlowStatus::CONTINUE_TO_SEND)) { 659 | indication (*theirAddress, {}, Result::N_ERROR); 660 | // Terminate the current reception of segmented message. 661 | transportMessagesMap.erase (transportMessagesMap.find (*theirAddress)); 662 | } 663 | } 664 | 665 | if (transportMessage.multiFrameRemainingLen) { 666 | return true; 667 | } 668 | 669 | indication (*theirAddress, transportMessage.data, Result::N_OK); 670 | transportMessagesMap.erase (iter); 671 | 672 | } break; 673 | 674 | case IsoNPduType::FLOW_FRAME: { 675 | if (Status s = stateMachine.run (&frame); s != Status::OK) { 676 | errorHandler (s); 677 | return false; 678 | } 679 | 680 | } break; 681 | 682 | default: 683 | break; // Ignore unidentified N_PDU. See Table 18. 684 | } 685 | 686 | return false; 687 | } 688 | 689 | /*****************************************************************************/ 690 | 691 | template void TransportProtocol::run () 692 | { 693 | // Check for timeouts between CAN frames while receiving. 694 | for (auto i = transportMessagesMap.begin (); i != transportMessagesMap.end ();) { 695 | auto &tpMsg = i->second; 696 | 697 | if (tpMsg.timer.isExpired ()) { 698 | indication (i->first, {}, i->second.timeoutReason); 699 | auto j = i; 700 | ++i; 701 | transportMessagesMap.erase (j); 702 | continue; 703 | } 704 | 705 | ++i; 706 | } 707 | 708 | // Run state machine(s) if any to perform transmission. 709 | if (Status s = stateMachine.run (); s != Status::OK) { 710 | errorHandler (s); 711 | return; 712 | } 713 | } 714 | 715 | /*****************************************************************************/ 716 | 717 | template bool TransportProtocol::sendFlowFrame (Address const &outgoingAddress, FlowStatus fs) 718 | { 719 | CanFrameWrapperType fcCanFrame; 720 | 721 | if (!AddressEncoderT::toFrame (outgoingAddress, fcCanFrame)) { 722 | errorHandler (Status::ADDRESS_ENCODE_ERROR); 723 | return false; 724 | } 725 | 726 | fcCanFrame.set (AddressTraitsT::N_PCI_OFSET + 0, (uint8_t (IsoNPduType::FLOW_FRAME) << 4) | uint8_t (fs)); 727 | fcCanFrame.set (AddressTraitsT::N_PCI_OFSET + 1, blockSize); // BS 728 | fcCanFrame.set (AddressTraitsT::N_PCI_OFSET + 2, separationTime); // Stmin 729 | fcCanFrame.setDlc (3 + AddressTraitsT::N_PCI_OFSET); 730 | 731 | if (!outputInterface (fcCanFrame.value ())) { 732 | errorHandler (Status::SEND_FAILED); 733 | return false; 734 | } 735 | 736 | return true; 737 | } 738 | 739 | /*****************************************************************************/ 740 | 741 | template 742 | int TransportProtocol::TransportMessage::append (CanFrameWrapperType const &frame, size_t offset, size_t len) 743 | { 744 | for (size_t inputIndex = 0; inputIndex < len; ++inputIndex) { 745 | data.insert (data.end (), frame.get (inputIndex + offset)); 746 | } 747 | 748 | return len; 749 | } 750 | 751 | /*****************************************************************************/ 752 | 753 | template Status TransportProtocol::StateMachine::run (CanFrameWrapperType const *frame) 754 | { 755 | if (state == State::DONE) { 756 | return Status::OK; 757 | } 758 | 759 | if (state != State::IDLE && state != State::SEND_FIRST_FRAME && bsCrTimer.isExpired ()) { 760 | if (state == State::RECEIVE_BS_FLOW_CONTROL_FRAME || state == State::RECEIVE_FIRST_FLOW_CONTROL_FRAME) { 761 | tp.confirm (myAddress, Result::N_TIMEOUT_BS); 762 | } 763 | else { 764 | tp.confirm (myAddress, Result::N_TIMEOUT_CR); 765 | } 766 | 767 | state = State::DONE; 768 | } 769 | 770 | IsoMessageT const &message = this->message; 771 | uint16_t isoMessageSize = message.size (); 772 | using Traits = AddressTraits; 773 | 774 | switch (state) { 775 | case State::IDLE: 776 | state = State::SEND_FIRST_FRAME; 777 | break; 778 | 779 | case State::SEND_FIRST_FRAME: { 780 | 781 | CanFrameWrapperType canFrame (0x00, false, (int (IsoNPduType::FIRST_FRAME) << 4) | (isoMessageSize & 0xf00) >> 8, 782 | (isoMessageSize & 0x0ff)); 783 | 784 | if (!AddressEncoderT::toFrame (myAddress, canFrame)) { 785 | return Status::ADDRESS_ENCODE_ERROR; 786 | } 787 | 788 | int toSend = std::min (isoMessageSize, 6); 789 | 790 | for (int i = 0; i < toSend; ++i) { 791 | canFrame.set (i + 2, message.at (i)); 792 | } 793 | 794 | canFrame.setDlc (2 + toSend); 795 | 796 | if (!outputInterface (canFrame.value ())) { 797 | tp.confirm (myAddress, Result::N_TIMEOUT_A); // TODO is it correct Result::? 798 | state = State::DONE; 799 | break; 800 | } 801 | 802 | tp.confirm (myAddress, Result::N_OK); 803 | state = State::RECEIVE_FIRST_FLOW_CONTROL_FRAME; 804 | bytesSent += toSend; 805 | bsCrTimer.start (N_BS_TIMEOUT); 806 | } break; 807 | 808 | case State::RECEIVE_BS_FLOW_CONTROL_FRAME: 809 | case State::RECEIVE_FIRST_FLOW_CONTROL_FRAME: { 810 | if (!separationTimer.isExpired ()) { 811 | break; 812 | } 813 | 814 | if (!frame) { 815 | break; 816 | } 817 | 818 | // Address as received in the CAN frame. 819 | auto theirAddress = AddressEncoderT::fromFrame (*frame); 820 | 821 | if (!theirAddress || !AddressEncoderT::matches (*theirAddress, myAddress)) { 822 | break; 823 | } 824 | 825 | IsoNPduType type = Traits::getType (*frame); 826 | 827 | if (type != IsoNPduType::FLOW_FRAME) { 828 | break; 829 | } 830 | 831 | FlowStatus fs = Traits::getFlowStatus (*frame); 832 | 833 | if (fs != FlowStatus::CONTINUE_TO_SEND && fs != FlowStatus::WAIT && fs != FlowStatus::OVERFLOWED) { 834 | tp.confirm (*theirAddress, Result::N_INVALID_FS); // 6.5.5.3 835 | state = State::DONE; // abort 836 | } 837 | 838 | if (fs == FlowStatus::OVERFLOWED) { 839 | tp.confirm (*theirAddress, Result::N_BUFFER_OVFLW); 840 | state = State::DONE; // abort 841 | } 842 | 843 | if (fs == FlowStatus::WAIT) { 844 | bsCrTimer.start (N_BS_TIMEOUT); 845 | ++waitFrameNumber; 846 | 847 | if (waitFrameNumber >= MAX_WAIT_FRAME_NUMBER) { // In case of MAX_WAIT_FRAME_NUMBER == 0 message will be aborted 848 | // immediately, which is fine according to the ISO. 849 | tp.confirm (*theirAddress, Result::N_WFT_OVRN); 850 | state = State::DONE; // abort 851 | } 852 | 853 | break; // state stays at RECEIVE_*_FLOW_CONTROL_FRAME 854 | } 855 | 856 | if (state == State::RECEIVE_FIRST_FLOW_CONTROL_FRAME) { 857 | receivedBlockSize = frame->get (1); // 6.5.5.4 page 21 858 | receivedSeparationTimeUs = frame->get (2); 859 | 860 | if (receivedSeparationTimeUs >= 0 && receivedSeparationTimeUs <= 0x7f) { 861 | receivedSeparationTimeUs *= 1000; // Convert to µs 862 | } 863 | else if (receivedSeparationTimeUs >= 0xf1 && receivedSeparationTimeUs <= 0xf9) { 864 | receivedSeparationTimeUs = (receivedSeparationTimeUs - 0xf0) * 100; 865 | } 866 | else { 867 | receivedSeparationTimeUs = uint32_t (0x7f) * 1000; // 6.5.5.6 ST error handling 868 | } 869 | } 870 | 871 | waitFrameNumber = 0; 872 | separationTimer.start (0); // Separation timer is started later with proper timeout calculated here. 873 | state = State::SEND_CONSECUTIVE_FRAME; 874 | bsCrTimer.start (N_CR_TIMEOUT); 875 | } break; 876 | 877 | case State::SEND_CONSECUTIVE_FRAME: { 878 | if (!separationTimer.isExpired ()) { 879 | break; 880 | } 881 | 882 | CanFrameWrapperType canFrame (0x00, true, (int (IsoNPduType::CONSECUTIVE_FRAME) << 4) | sequenceNumber); 883 | 884 | if (!AddressEncoderT::toFrame (myAddress, canFrame)) { 885 | return Status::ADDRESS_ENCODE_ERROR; 886 | } 887 | 888 | ++sequenceNumber; 889 | sequenceNumber %= 16; 890 | 891 | int toSend = std::min (isoMessageSize - bytesSent, 7); 892 | for (int i = 0; i < toSend; ++i) { 893 | canFrame.set (i + 1, message.at (i + bytesSent)); 894 | } 895 | 896 | canFrame.setDlc (1 + toSend); 897 | 898 | if (!outputInterface (canFrame.value ())) { 899 | tp.confirm (myAddress, Result::N_TIMEOUT_A); 900 | state = State::DONE; 901 | break; 902 | } 903 | 904 | bytesSent += toSend; 905 | 906 | if (bytesSent >= message.size ()) { 907 | state = State::DONE; 908 | break; 909 | } 910 | 911 | if (receivedBlockSize && ++blocksSent >= receivedBlockSize) { 912 | state = State::RECEIVE_BS_FLOW_CONTROL_FRAME; 913 | bsCrTimer.start (N_BS_TIMEOUT); 914 | break; 915 | } 916 | 917 | // TODO separationTimeUs should be in 100µs units. Now i have 1ms resolution, so f1-f9 STmin are rounded to 0 918 | separationTimer.start (receivedSeparationTimeUs / 1000); 919 | bsCrTimer.start (N_CR_TIMEOUT); 920 | break; 921 | 922 | } break; 923 | 924 | default: 925 | break; 926 | } 927 | 928 | return Status::OK; 929 | } 930 | 931 | } // namespace tp 932 | --------------------------------------------------------------------------------