From c78903e83c7552a90a28853f4cb621fbb632628d Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Sat, 16 Dec 2023 10:57:28 +0000 Subject: [PATCH 1/9] feat(tp): complete rewrite of transport protocol with unit-tests --- isobus/CMakeLists.txt | 6 +- .../can_general_parameter_group_numbers.hpp | 4 +- isobus/include/isobus/isobus/can_message.hpp | 4 + .../isobus/isobus/can_message_data.hpp | 172 ++ .../isobus/can_network_configuration.hpp | 13 +- .../isobus/isobus/can_network_manager.hpp | 4 +- .../isobus/isobus/can_transport_protocol.hpp | 343 ++- .../src/can_extended_transport_protocol.cpp | 1 - isobus/src/can_message.cpp | 2 +- isobus/src/can_message_data.cpp | 116 + isobus/src/can_network_configuration.cpp | 10 + isobus/src/can_network_manager.cpp | 69 +- isobus/src/can_transport_protocol.cpp | 1588 ++++++------ test/CMakeLists.txt | 3 +- test/diagnostic_protocol_tests.cpp | 12 +- test/transport_protocol_tests.cpp | 2141 +++++++++++++++++ utility/include/isobus/utility/data_span.hpp | 79 + 17 files changed, 3759 insertions(+), 808 deletions(-) create mode 100644 isobus/include/isobus/isobus/can_message_data.hpp create mode 100644 isobus/src/can_message_data.cpp create mode 100644 test/transport_protocol_tests.cpp create mode 100644 utility/include/isobus/utility/data_span.hpp diff --git a/isobus/CMakeLists.txt b/isobus/CMakeLists.txt index 4805b1a0..4ab484d5 100644 --- a/isobus/CMakeLists.txt +++ b/isobus/CMakeLists.txt @@ -40,7 +40,8 @@ set(ISOBUS_SRC "isobus_maintain_power_interface.cpp" "isobus_virtual_terminal_objects.cpp" "nmea2000_message_definitions.cpp" - "nmea2000_message_interface.cpp") + "nmea2000_message_interface.cpp" + "can_message_data.cpp") # Prepend the source directory path to all the source files prepend(ISOBUS_SRC ${ISOBUS_SRC_DIR} ${ISOBUS_SRC}) @@ -82,7 +83,8 @@ set(ISOBUS_INCLUDE "isobus_maintain_power_interface.hpp" "isobus_virtual_terminal_objects.hpp" "nmea2000_message_definitions.hpp" - "nmea2000_message_interface.hpp") + "nmea2000_message_interface.hpp" + "can_message_data.hpp") # Prepend the include directory path to all the include files prepend(ISOBUS_INCLUDE ${ISOBUS_INCLUDE_DIR} ${ISOBUS_INCLUDE}) diff --git a/isobus/include/isobus/isobus/can_general_parameter_group_numbers.hpp b/isobus/include/isobus/isobus/can_general_parameter_group_numbers.hpp index e82d093b..ebbfb386 100644 --- a/isobus/include/isobus/isobus/can_general_parameter_group_numbers.hpp +++ b/isobus/include/isobus/isobus/can_general_parameter_group_numbers.hpp @@ -28,8 +28,8 @@ namespace isobus ECUtoVirtualTerminal = 0xE700, Acknowledge = 0xE800, ParameterGroupNumberRequest = 0xEA00, - TransportProtocolData = 0xEB00, - TransportProtocolCommand = 0xEC00, + TransportProtocolDataTransfer = 0xEB00, + TransportProtocolConnectionManagement = 0xEC00, AddressClaim = 0xEE00, ProprietaryA = 0xEF00, MachineSelectedSpeed = 0xF022, diff --git a/isobus/include/isobus/isobus/can_message.hpp b/isobus/include/isobus/isobus/can_message.hpp index 0987df41..cc83ebc6 100644 --- a/isobus/include/isobus/isobus/can_message.hpp +++ b/isobus/include/isobus/isobus/can_message.hpp @@ -13,11 +13,15 @@ #include "isobus/isobus/can_control_function.hpp" #include "isobus/isobus/can_identifier.hpp" +#include "isobus/utility/data_span.hpp" #include namespace isobus { + /// @brief A read-only span of data for a CAN message + using CANDataSpan = DataSpan; + //================================================================================================ /// @class CANMessage /// diff --git a/isobus/include/isobus/isobus/can_message_data.hpp b/isobus/include/isobus/isobus/can_message_data.hpp new file mode 100644 index 00000000..d77cf1d7 --- /dev/null +++ b/isobus/include/isobus/isobus/can_message_data.hpp @@ -0,0 +1,172 @@ +//================================================================================================ +/// @file can_message_data.hpp +/// +/// @brief An interface class that represents data payload of a CAN message of arbitrary length. +/// @author Daan Steenbergen +/// +/// @copyright 2023 - The OpenAgriculture Developers +//================================================================================================ + +#ifndef CAN_MESSAGE_DATA_HPP +#define CAN_MESSAGE_DATA_HPP + +#include "isobus/isobus/can_callbacks.hpp" +#include "isobus/isobus/can_control_function.hpp" +#include "isobus/isobus/can_message.hpp" +#include "isobus/utility/data_span.hpp" + +#include +#include +#include + +namespace isobus +{ + //================================================================================================ + /// @class CANMessageData + /// + /// @brief A interface class that represents data payload of a CAN message of arbitrary length. + //================================================================================================ + class CANMessageData + { + public: + /// @brief Default destructor. + virtual ~CANMessageData() = default; + + /// @brief Get the size of the data. + /// @return The size of the data. + virtual std::size_t size() const = 0; + + /// @brief Get the byte at the given index. + /// @param[in] index The index of the byte to get. + /// @return The byte at the given index. + virtual std::uint8_t get_byte(std::size_t index) = 0; + + /// @brief If the data isn't owned by this class, make a copy of the data. + /// @param[in] self A pointer to this object. + /// @return A copy of the data if it isn't owned by this class, otherwise a moved pointer. + virtual std::unique_ptr copy_if_not_owned(std::unique_ptr self) const = 0; + }; + + //================================================================================================ + /// @class CANMessageDataVector + /// + /// @brief A class that represents data of a CAN message by holding a vector of bytes. + //================================================================================================ + class CANMessageDataVector : public CANMessageData + , public std::vector + { + public: + /// @brief Construct a new CANMessageDataVector object. + /// @param[in] size The size of the data. + explicit CANMessageDataVector(std::size_t size); + + /// @brief Construct a new CANMessageDataVector object. + /// @param[in] data The data to copy. + explicit CANMessageDataVector(const std::vector &data); + + /// @brief Construct a new CANMessageDataVector object. + /// @param[in] data A pointer to the data to copy. + /// @param[in] size The size of the data to copy. + CANMessageDataVector(const std::uint8_t *data, std::size_t size); + + /// @brief Get the size of the data. + /// @return The size of the data. + std::size_t size() const override; + + /// @brief Get the byte at the given index. + /// @param[in] index The index of the byte to get. + /// @return The byte at the given index. + std::uint8_t get_byte(std::size_t index) override; + + /// @brief Set the byte at the given index. + /// @param[in] index The index of the byte to set. + /// @param[in] value The value to set the byte to. + void set_byte(std::size_t index, std::uint8_t value); + + /// @brief Get the data span. + /// @return The data span. + CANDataSpan data() const; + + /// @brief If the data isn't owned by this class, make a copy of the data. + /// @param[in] self A pointer to this object. + /// @return A copy of the data if it isn't owned by this class, otherwise it returns itself. + std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; + }; + + //================================================================================================ + /// @class CANMessageDataView + /// + /// @brief A class that represents data of a CAN message by holding a view of an array of bytes. + /// The view is not owned by this class, it is simply holding a pointer to the array of bytes. + //================================================================================================ + class CANMessageDataView : public CANMessageData + , public CANDataSpan + { + public: + /// @brief Construct a new CANMessageDataView object. + /// @param[in] ptr The pointer to the array of bytes. + /// @param[in] len The length of the array of bytes. + CANMessageDataView(const std::uint8_t *ptr, std::size_t len); + + /// @brief Get the size of the data. + /// @return The size of the data. + std::size_t size() const override; + + /// @brief Get the byte at the given index. + /// @param[in] index The index of the byte to get. + /// @return The byte at the given index. + std::uint8_t get_byte(std::size_t index) override; + + /// @brief Get the data span. + /// @return The data span. + CANDataSpan data() const; + + /// @brief If the data isn't owned by this class, make a copy of the data. + /// @param[in] self A pointer to this object. + /// @return A copy of the data if it isn't owned by this class, otherwise it returns itself. + std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; + }; + + //================================================================================================ + /// @class CANMessageDataCallback + /// + /// @brief A class that represents data of a CAN message by using a callback function. + //================================================================================================ + class CANMessageDataCallback : public CANMessageData + { + public: + /// @brief Constructor for transport data that uses a callback function. + /// @param[in] size The size of the data. + /// @param[in] callback The callback function to be called for each data chunk. + /// @param[in] parentPointer The parent object that owns this callback (optional). + /// @param[in] chunkSize The size of each data chunk (optional, default is 7). + CANMessageDataCallback(std::size_t size, + DataChunkCallback callback, + void *parentPointer = nullptr, + std::size_t chunkSize = 7); + + /// @brief Get the size of the data. + /// @return The size of the data. + std::size_t size() const override; + + /// @brief Get the byte at the given index. + /// @param[in] index The index of the byte to get. + /// @return The byte at the given index. + std::uint8_t get_byte(std::size_t index) override; + + /// @brief If the data isn't owned by this class, make a copy of the data. + /// @param[in] self A pointer to this object. + /// @return A copy of the data if it isn't owned by this class, otherwise it returns itself. + std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; + + private: + std::size_t totalSize; ///< The total size of the data. + DataChunkCallback callback; ///< The callback function to be called for each data chunk. + void *parentPointer; ///< The parent object that gets passed to the callback function. + std::vector buffer; ///< The buffer to store the data chunks. + std::size_t bufferSize; ///< The size of the buffer. + std::size_t dataOffset = 0; ///< The offset of the data in the buffer. + }; +} // namespace isobus + +#endif // CAN_MESSAGE_DATA_HPP diff --git a/isobus/include/isobus/isobus/can_network_configuration.hpp b/isobus/include/isobus/isobus/can_network_configuration.hpp index 8b6cd129..a7c92787 100644 --- a/isobus/include/isobus/isobus/can_network_configuration.hpp +++ b/isobus/include/isobus/isobus/can_network_configuration.hpp @@ -37,7 +37,7 @@ namespace isobus /// @returns The max number of concurrent TP sessions std::uint32_t get_max_number_transport_protocol_sessions() const; - /// @brief Sets the minimum time to wait between sending BAM frames + /// @brief Sets the minimum time to wait between sending BAM frames (default is 50 ms) /// @details The acceptable range as defined by ISO-11783 is 10 to 200 ms. /// This is a minumum time, so if you set it to some value, like 10 ms, the /// stack will attempt to transmit it as close to that time as it can, but it is @@ -73,6 +73,16 @@ namespace isobus /// @returns The max number of frames to use in transport protocols in each network manager update std::uint8_t get_max_number_of_network_manager_protocol_frames_per_update() const; + /// @brief Set the the number of packets per CTS message for TP sessions. The default + /// is 16. Note that the receiving control function may not support this limitation, or choose + /// to ignore it and use a different number of packets per CTS packet. + /// @param[in] numberPackets The number of packets per CTS packet for TP sessions. + void set_number_of_packets_per_cts_message(std::uint8_t numberPackets); + + /// @brief Get the the number of packets per CTS packet for TP sessions. + /// @returns The number of packets per CTS packet for TP sessions. + std::uint8_t get_number_of_packets_per_cts_message() const; + private: static constexpr std::uint8_t DEFAULT_BAM_PACKET_DELAY_TIME_MS = 50; ///< The default time between BAM frames, as defined by J1939 @@ -80,6 +90,7 @@ namespace isobus std::uint32_t minimumTimeBetweenTransportProtocolBAMFrames = DEFAULT_BAM_PACKET_DELAY_TIME_MS; ///< The configurable time between BAM frames std::uint8_t extendedTransportProtocolMaxNumberOfFramesPerEDPO = 0xFF; ///< Used to control throttling of ETP sessions. std::uint8_t networkManagerMaxFramesToSendPerUpdate = 0xFF; ///< Used to control the max number of transport layer frames added to the driver queue per network manager update + std::uint8_t numberOfPacketsPerCTSMessage = 16; ///< The number of packets per CTS message for TP sessions }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/can_network_manager.hpp b/isobus/include/isobus/isobus/can_network_manager.hpp index b9b16732..3ad64cd3 100644 --- a/isobus/include/isobus/isobus/can_network_manager.hpp +++ b/isobus/include/isobus/isobus/can_network_manager.hpp @@ -371,9 +371,9 @@ namespace isobus static constexpr std::uint32_t BUSLOAD_UPDATE_FREQUENCY_MS = 100; ///< Bus load bit accumulation happens over a 100ms window CANNetworkConfiguration configuration; ///< The configuration for this network manager - ExtendedTransportProtocolManager extendedTransportProtocol; ///< Static instance of the protocol manager + ExtendedTransportProtocolManager extendedTransportProtocol; ///< Instance of the extended transport protocol manager FastPacketProtocol fastPacketProtocol; ///< Instance of the fast packet protocol - TransportProtocolManager transportProtocol; ///< Static instance of the transport protocol manager + TransportProtocolManager transportProtocol; ///< Instance of the transport protocol manager std::array, CAN_PORT_MAXIMUM> busloadMessageBitsHistory; ///< Stores the approximate number of bits processed on each channel over multiple previous time windows std::array currentBusloadBitAccumulator; ///< Accumulates the approximate number of bits processed on each channel during the current time window diff --git a/isobus/include/isobus/isobus/can_transport_protocol.hpp b/isobus/include/isobus/isobus/can_transport_protocol.hpp index 967b1cbc..2a5d5668 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol.hpp @@ -4,6 +4,7 @@ /// @brief A protocol that handles the ISO11783/J1939 transport protocol. /// It handles both the broadcast version (BAM) and and the connection mode version. /// @author Adrian Del Grosso +/// @author Daan Steenbergen /// /// @copyright 2022 Adrian Del Grosso //================================================================================================ @@ -11,9 +12,13 @@ #ifndef CAN_TRANSPORT_PROTOCOL_HPP #define CAN_TRANSPORT_PROTOCOL_HPP -#include "isobus/isobus/can_badge.hpp" #include "isobus/isobus/can_control_function.hpp" -#include "isobus/isobus/can_protocol.hpp" +#include "isobus/isobus/can_message.hpp" +#include "isobus/isobus/can_message_data.hpp" +#include "isobus/isobus/can_message_frame.hpp" +#include "isobus/isobus/can_network_configuration.hpp" + +#include namespace isobus { @@ -22,15 +27,15 @@ namespace isobus /// /// @brief A class that handles the ISO11783/J1939 transport protocol. /// @details This class handles transmission and reception of CAN messages up to 1785 bytes. - /// Both broadcast and connection mode are supported. Simply call send_can_message on the - /// network manager with an appropriate data length, and the protocol will be automatically - /// selected to be used. As a note, use of BAM is discouraged, as it has profound + /// Both broadcast and connection mode are supported. Simply call `CANNetworkManager::send_can_message()` + /// with an appropriate data length, and the protocol will be automatically selected to be used. + /// @note The use of broadcast messages is discouraged, as it has profound /// packet timing implications for your application, and is limited to only 1 active session at a time. /// That session could be busy if you are using DM1 or any other BAM protocol, causing intermittent /// transmit failures from this class. This is not a bug, rather a limitation of the protocol /// definition. //================================================================================================ - class TransportProtocolManager : public CANLibProtocol + class TransportProtocolManager { public: /// @brief The states that a TP session could be in. Used for the internal state machine. @@ -61,38 +66,170 @@ namespace isobus Receive ///< We are receiving a message }; - /// @brief A useful way to compare sesson objects to each other for equality + /// @brief A useful way to compare session objects to each other for equality /// @param[in] obj The object to compare to /// @returns true if the objects are equal, false if not - bool operator==(const TransportProtocolSession &obj); + bool operator==(const TransportProtocolSession &obj) const; + + /// @brief Checks if the source and destination control functions match the given control functions. + /// @param[in] other_source The control function to compare with the source control function. + /// @param[in] other_destination The control function to compare with the destination control function. + /// @returns True if the source and destination control functions match the given control functions, false otherwise. + bool matches(std::shared_ptr other_source, std::shared_ptr other_destination) const; + + /// @brief Get the direction of the session + /// @return The direction of the session + Direction get_direction() const; + + /// @brief Get the state of the session + /// @return The state of the session + StateMachineState get_state() const; /// @brief Get the total number of bytes that will be sent or received in this session /// @return The length of the message in number of bytes - std::uint32_t get_message_data_length() const; + std::uint32_t get_message_length() const; - private: + /// @brief Get the data buffer for the session + /// @return The data buffer for the session + CANMessageData &get_data() const; + + /// @brief Get the control function that is sending the message + /// @return The source control function + std::shared_ptr get_source() const; + + /// @brief Get the control function that is receiving the message + /// @return The destination control function + std::shared_ptr get_destination() const; + + /// @brief Get the parameter group number of the message + /// @return The PGN of the message + std::uint32_t get_parameter_group_number() const; + + /// @brief Get whether or not this session is a broadcast session (BAM) + /// @return True if this session is a broadcast session, false if not + bool is_broadcast() const; + + protected: friend class TransportProtocolManager; ///< Allows the TP manager full access - /// @brief The constructor for a TP session - /// @param[in] sessionDirection Tx or Rx - /// @param[in] canPortIndex The CAN channel index for the session - TransportProtocolSession(Direction sessionDirection, std::uint8_t canPortIndex); + /// @brief Factory method to create a new receive session + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @returns A new receive session + static TransportProtocolSession create_receive_session(std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination); + + /// @brief Factory method to create a new transmit session + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + /// @returns A new transmit session + static TransportProtocolSession create_transmit_session(std::uint32_t parameterGroupNumber, + std::unique_ptr data, + std::shared_ptr source, + std::shared_ptr destination, + std::uint8_t clearToSendPacketMax, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); + + /// @brief Set the state of the session + /// @param[in] value The state to set the session to + void set_state(StateMachineState value); + + /// @brief Get the number of packets to be sent in response to the current CTS + /// @return The number of packets to be sent in response to the current CTS + std::uint8_t get_cts_number_of_packets_remaining() const; + + /// @brief Set the number of packets to be sent in response to the curent CTS + /// @param[in] value The number of packets to be sent in response to the curent CTS + void set_cts_number_of_packets(std::uint8_t value); + + /// @brief Get the number of packets that can be sent in response to the current CTS + /// @return The number of packets that can be sent in response to the current CTS + std::uint8_t get_cts_number_of_packets() const; + + /// @brief Get the maximum number of packets that can be sent per CTS as indicated by the RTS message + /// @return The maximum number of packets that can be sent per CTS as indicated by the RTS message + std::uint8_t get_rts_number_of_packet_limit() const; + + /// @brief Get the last sequence number that was processed + /// @return The last sequence number that was processed + std::uint8_t get_last_sequence_number() const; + + /// @brief Get the last packet number that was processed + /// @return The last packet number that was processed + std::uint8_t get_last_packet_number() const; + + /// @brief Set the last sequence number that will be processed + /// @param[in] value The last sequence number that will be processed + void set_last_sequency_number(std::uint8_t value); + + /// @brief Set the last acknowledged packet number by the receiver + /// @param[in] value The last acknowledged packet number by the receiver + void set_acknowledged_packet_number(std::uint8_t value); + + /// @brief Get the number of packets that remain to be sent or received in this session + /// @return The number of packets that remain to be sent or received in this session + std::uint8_t get_number_of_remaining_packets() const; + + /// @brief Get the total number of packets that will be sent or received in this session + /// @return The total number of packets that will be sent or received in this session + std::uint8_t get_total_number_of_packets() const; - /// @brief The destructor for a TP session - ~TransportProtocolSession() = default; + private: + /// @brief The constructor for a session + /// @param[in] direction The direction of the session + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + TransportProtocolSession(Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); StateMachineState state = StateMachineState::None; ///< The state machine state for this session - CANMessage sessionMessage; ///< A CAN message is used in the session to represent and store data like PGN + + Direction direction; ///< The direction of the session + std::uint32_t parameterGroupNumber; ///< The PGN of the message + std::unique_ptr data; ///< The data buffer for the message + std::uint32_t totalMessageSize; ///< The total size of the message in bytes + std::shared_ptr source; ///< The source control function + std::shared_ptr destination; ///< The destination control function + + std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts + std::uint8_t lastSequenceNumber = 0; ///< The last processed sequence number for this set of packets + std::uint8_t lastAcknowledgedPacketNumber = 0; ///< The last acknowledged packet number by the receiver + + std::uint8_t totalNumberOfPackets; ///< The total number of packets that will be sent or received in this session + std::uint8_t clearToSendPacketCount = 0; ///< The number of packets to be sent in response to one CTS + std::uint8_t clearToSendPacketCountMax = 0xFF; ///< The max packets that can be sent per CTS as indicated by the RTS message + TransmitCompleteCallback sessionCompleteCallback = nullptr; ///< A callback that is to be called when the session is completed - DataChunkCallback frameChunkCallback = nullptr; ///< A callback that might be used to get chunks of data to send - std::uint32_t frameChunkCallbackMessageLength = 0; ///< The length of the message that is being sent in chunks void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr - std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts - std::uint16_t lastPacketNumber = 0; ///< The last processed sequence number for this set of packets - std::uint8_t packetCount = 0; ///< The total number of packets to receive or send in this session - std::uint8_t processedPacketsThisSession = 0; ///< The total processed packet count for the whole session so far - std::uint8_t clearToSendPacketMax = 0; ///< The max packets that can be sent per CTS as indicated by the RTS message - const Direction sessionDirection; ///< Represents Tx or Rx session }; /// @brief A list of all defined abort reasons in ISO11783 @@ -111,6 +248,13 @@ namespace isobus AnyOtherError = 250 ///< Any other error not enumerated above, 0xFE }; + using SendCANFrameCallback = std::function sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority)>; ///< A callback for sending a CAN frame + using CANMessageReceivedCallback = std::function; ///< A callback for when a complete CAN message is received using the TP protocol + static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 0x10; ///< TP.CM_RTS Multiplexor static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 0x11; ///< TP.CM_CTS Multiplexor static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 0x13; ///< TP.CM_EOM_ACK Multiplexor @@ -124,116 +268,149 @@ namespace isobus static constexpr std::uint8_t MESSAGE_TR_TIMEOUT_MS = 200; ///< The Tr Timeout as defined by the standard static constexpr std::uint8_t PROTOCOL_BYTES_PER_FRAME = 7; ///< The number of payload bytes per frame minus overhead of sequence number - /// @brief The constructor for the TransportProtocolManager - TransportProtocolManager() = default; - - /// @brief The destructor for the TransportProtocolManager - ~TransportProtocolManager() final; + /// @brief The constructor for the TransportProtocolManager, for advanced use only. + /// In most cases, you should use the CANNetworkManager::send_can_message() function to transmit messages. + /// @param[in] sendCANFrameCallback A callback for sending a CAN frame to hardware + /// @param[in] canMessageReceivedCallback A callback for when a complete CAN message is received using the TP protocol + /// @param[in] configuration The configuration to use for this protocol + TransportProtocolManager(const SendCANFrameCallback &sendCANFrameCallback, + const CANMessageReceivedCallback &canMessageReceivedCallback, + const CANNetworkConfiguration *configuration); - /// @brief The protocol's initializer function - void initialize(CANLibBadge) override; + /// @brief Updates all sessions managed by this protocol manager instance. + void update(); - /// @brief A generic way for a protocol to process a received message - /// @param[in] message A received CAN message - void process_message(const CANMessage &message) override; + /// @brief Checks if the source and destination control function have an active session/connection. + /// @param[in] source The source control function for the session + /// @param[in] destination The destination control function for the session + /// @returns true if a matching session was found, false if not + bool has_session(std::shared_ptr source, std::shared_ptr destination); /// @brief A generic way for a protocol to process a received message /// @param[in] message A received CAN message - /// @param[in] parent Provides the context to the actual TP manager object - static void process_message(const CANMessage &message, void *parent); + void process_message(const CANMessage &message); /// @brief The network manager calls this to see if the protocol can accept a long CAN message for processing /// @param[in] parameterGroupNumber The PGN of the message /// @param[in] data The data to be sent - /// @param[in] messageLength The length of the data to be sent /// @param[in] source The source control function /// @param[in] destination The destination control function - /// @param[in] transmitCompleteCallback A callback for when the protocol completes its work + /// @param[in] sessionCompleteCallback A callback for when the protocol completes its work /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - /// @param[in] frameChunkCallback A callback to get some data to send /// @returns true if the message was accepted by the protocol for processing bool protocol_transmit_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *data, - std::uint32_t messageLength, + std::unique_ptr &data, std::shared_ptr source, std::shared_ptr destination, - TransmitCompleteCallback transmitCompleteCallback, - void *parentPointer, - DataChunkCallback frameChunkCallback) override; - - /// @brief Updates the protocol cyclically - void update(CANLibBadge) override; + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); private: /// @brief Aborts the session with the specified abort reason. Sends a CAN message. /// @param[in] session The session to abort /// @param[in] reason The reason we're aborting the session /// @returns true if the abort was send OK, false if not sent - bool abort_session(TransportProtocolSession *session, ConnectionAbortReason reason); + bool abort_session(const TransportProtocolSession &session, ConnectionAbortReason reason); - /// @brief Aborts Tp with no corresponding session with the specified abort reason. Sends a CAN message. + /// @brief Send an abort with no corresponding session with the specified abort reason. Sends a CAN message. + /// @param[in] sender The sender of the abort + /// @param[in] receiver The receiver of the abort /// @param[in] parameterGroupNumber The PGN of the TP "session" we're aborting /// @param[in] reason The reason we're aborting the session - /// @param[in] source The source control function from which we'll send the abort - /// @param[in] destination The destination control function to which we'll send the abort /// @returns true if the abort was send OK, false if not sent - bool abort_session(std::uint32_t parameterGroupNumber, ConnectionAbortReason reason, std::shared_ptr source, std::shared_ptr destination); + bool send_abort(std::shared_ptr sender, + std::shared_ptr receiver, + std::uint32_t parameterGroupNumber, + ConnectionAbortReason reason) const; /// @brief Gracefully closes a session to prepare for a new session /// @param[in] session The session to close - /// @param[in] successfull Denotes if the session was successful - void close_session(TransportProtocolSession *session, bool successfull); - - /// @brief Processes end of session callbacks - /// @param[in] session The session we've just completed - /// @param[in] success Denotes if the session was successful - void process_session_complete_callback(TransportProtocolSession *session, bool success); + /// @param[in] successful Denotes if the session was successful + void close_session(const TransportProtocolSession &session, bool successful); /// @brief Sends the "broadcast announce" message /// @param[in] session The session for which we're sending the BAM /// @returns true if the BAM was sent, false if sending was not successful - bool send_broadcast_announce_message(TransportProtocolSession *session) const; + bool send_broadcast_announce_message(const TransportProtocolSession &session) const; /// @brief Sends the "clear to send" message /// @param[in] session The session for which we're sending the CTS /// @returns true if the CTS was sent, false if sending was not successful - bool send_clear_to_send(TransportProtocolSession *session) const; + bool send_clear_to_send(TransportProtocolSession &session) const; /// @brief Sends the "request to send" message as part of initiating a transmit /// @param[in] session The session for which we're sending the RTS /// @returns true if the RTS was sent, false if sending was not successful - bool send_request_to_send(TransportProtocolSession *session) const; + bool send_request_to_send(const TransportProtocolSession &session) const; /// @brief Sends the "end of message acknowledgement" message for the provided session /// @param[in] session The session for which we're sending the EOM ACK /// @returns true if the EOM was sent, false if sending was not successful - bool send_end_of_session_acknowledgement(TransportProtocolSession *session) const; - - /// @brief Sets the state machine state of the TP session - /// @param[in] session The session to update - /// @param[in] value The state to update the session to - void set_state(TransportProtocolSession *session, StateMachineState value); - - /// @brief Gets a TP session from the passed in source and destination combination - /// @param[in] source The source control function for the session - /// @param[in] destination The destination control function for the session - /// @param[out] session The found session, or nullptr if no session matched the supplied parameters - /// @returns true if a matching session was found, false if not - bool get_session(TransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination); + bool send_end_of_session_acknowledgement(const TransportProtocolSession &session) const; + + ///@brief Sends data transfer packets for the specified TransportProtocolSession. + /// @param[in] session The TransportProtocolSession for which to send data transfer packets. + void send_data_transfer_packets(TransportProtocolSession &session); + + /// @brief Processes a broadcast announce message. + /// @param[in] source The source control function that sent the broadcast announce message. + /// @param[in] parameterGroupNumber The Parameter Group Number of the broadcast announce message. + /// @param[in] totalMessageSize The total size of the broadcast announce message. + /// @param[in] totalNumberOfPackets The total number of packets in the broadcast announce message. + void process_broadcast_announce_message(const std::shared_ptr source, std::uint32_t parameterGroupNumber, std::uint16_t totalMessageSize, std::uint8_t totalNumberOfPackets); + + /// @brief Processes a request to send a message over the CAN transport protocol. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The Parameter Group Number of the message. + /// @param[in] totalMessageSize The total size of the message in bytes. + /// @param[in] totalNumberOfPackets The total number of packets to be sent. + /// @param[in] clearToSendPacketMax The maximum number of clear to send packets that can be sent. + void process_request_to_send(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint16_t totalMessageSize, std::uint8_t totalNumberOfPackets, std::uint8_t clearToSendPacketMax); + + /// @brief Processes the Clear To Send (CTS) message. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The Parameter Group Number (PGN) of the message. + /// @param[in] packetsToBeSent The number of packets to be sent. + /// @param[in] nextPacketNumber The next packet number. + void process_clear_to_send(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint8_t packetsToBeSent, std::uint8_t nextPacketNumber); + + /// @brief Processes the end of session acknowledgement. + /// @param[in] source The source control function. + /// @param[in] destination The destination control function. + /// @param[in] parameterGroupNumber The parameter group number. + void process_end_of_session_acknowledgement(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber); + + /// @brief Processes an abort message in the CAN transport protocol. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The PGN (Parameter Group Number) of the message. + /// @param[in] reason The reason for the connection abort. + void process_abort(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, TransportProtocolManager::ConnectionAbortReason reason); + + /// @brief Processes a connection management message. + /// @param[in] message The CAN message to be processed. + void process_connection_management_message(const CANMessage &message); + + /// @brief Processes a data transfer message. + /// @param[in] message The CAN message to be processed. + void process_data_transfer_message(const CANMessage &message); /// @brief Gets a TP session from the passed in source and destination and PGN combination /// @param[in] source The source control function for the session /// @param[in] destination The destination control function for the session - /// @param[in] parameterGroupNumber The PGN of the session - /// @param[out] session The found session, or nullptr if no session matched the supplied parameters - /// @returns true if a matching session was found, false if not - bool get_session(TransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination, std::uint32_t parameterGroupNumber); + /// @returns a matching session, or nullptr if no session matched the supplied parameters + TransportProtocolSession *get_session(std::shared_ptr source, std::shared_ptr destination); - /// @brief Updates the state machine of a Tp session + /// @brief Update the state machine for the passed in session /// @param[in] session The session to update - void update_state_machine(TransportProtocolSession *session); + void update_state_machine(TransportProtocolSession &session); - std::vector activeSessions; ///< A list of all active TP sessions + std::vector activeSessions; ///< A list of all active TP sessions + const SendCANFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame + const CANMessageReceivedCallback canMessageReceivedCallback; ///< A callback for when a complete CAN message is received using the TP protocol + const CANNetworkConfiguration *configuration; ///< The configuration to use for this protocol }; } // namespace isobus diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index 46cb0968..3c8de1c7 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -308,7 +308,6 @@ namespace isobus { send_end_of_session_acknowledgement(tempSession); } - CANNetworkManager::CANNetwork.process_any_control_function_pgn_callbacks(tempSession->sessionMessage); CANNetworkManager::CANNetwork.protocol_message_callback(tempSession->sessionMessage); close_session(tempSession, true); } diff --git a/isobus/src/can_message.cpp b/isobus/src/can_message.cpp index 2d879e99..238826c6 100644 --- a/isobus/src/can_message.cpp +++ b/isobus/src/can_message.cpp @@ -56,7 +56,7 @@ namespace isobus bool CANMessage::is_broadcast() const { - return identifier.get_destination_address() == CANIdentifier::GLOBAL_ADDRESS; + return (!has_valid_destination_control_function()) || (destination->get_address() == CANIdentifier::GLOBAL_ADDRESS); } bool CANMessage::is_destination_our_device() const diff --git a/isobus/src/can_message_data.cpp b/isobus/src/can_message_data.cpp new file mode 100644 index 00000000..050d3abb --- /dev/null +++ b/isobus/src/can_message_data.cpp @@ -0,0 +1,116 @@ +//================================================================================================ +/// @file can_message_data.hpp +/// +/// @brief An class that represents a CAN message of arbitrary length being transported. +/// @author Daan Steenbergen +/// +/// @copyright 2023 OpenAgriculture +//================================================================================================ + +#include "isobus/isobus/can_message_data.hpp" + +#include + +namespace isobus +{ + CANMessageDataVector::CANMessageDataVector(std::size_t size) + { + vector::resize(size); + } + + CANMessageDataVector::CANMessageDataVector(const std::vector &data) + { + vector::assign(data.begin(), data.end()); + } + + CANMessageDataVector::CANMessageDataVector(const std::uint8_t *data, std::size_t size) + { + vector::assign(data, data + size); + } + + std::size_t CANMessageDataVector::size() const + { + return vector::size(); + } + + std::uint8_t CANMessageDataVector::get_byte(std::size_t index) + { + return vector::at(index); + } + + void CANMessageDataVector::set_byte(std::size_t index, std::uint8_t value) + { + vector::at(index) = value; + } + + CANDataSpan CANMessageDataVector::data() const + { + return CANDataSpan(vector::data(), vector::size()); + } + + std::unique_ptr CANMessageDataVector::copy_if_not_owned(std::unique_ptr self) const + { + // We know that a CANMessageDataVector is always owned by itself, so we can just return itself. + return self; + } + + CANMessageDataView::CANMessageDataView(const std::uint8_t *ptr, std::size_t len) : + CANDataSpan(ptr, len) + { + } + + std::size_t CANMessageDataView::size() const + { + return DataSpan::size(); + } + + std::uint8_t CANMessageDataView::get_byte(std::size_t index) + { + return DataSpan::operator[](index); + } + + CANDataSpan CANMessageDataView::data() const + { + return CANDataSpan(DataSpan::begin(), DataSpan::size()); + } + + std::unique_ptr CANMessageDataView::copy_if_not_owned(std::unique_ptr) const + { + // A view doesn't own the data, so we need to make a copy. + return std::unique_ptr(new CANMessageDataVector(DataSpan::begin(), DataSpan::size())); + } + + CANMessageDataCallback::CANMessageDataCallback(std::size_t size, + DataChunkCallback callback, + void *parentPointer, + std::size_t chunkSize) : + totalSize(size), + callback(callback), + parentPointer(parentPointer), + bufferSize(chunkSize) + { + buffer.reserve(bufferSize); + } + + std::size_t CANMessageDataCallback::size() const + { + return totalSize; + } + + std::uint8_t CANMessageDataCallback::get_byte(std::size_t index) + { + if (index >= dataOffset + bufferSize) + { + dataOffset += bufferSize; + callback(0, dataOffset, std::min(totalSize - dataOffset, bufferSize), buffer.data(), parentPointer); + } + return buffer[index - dataOffset]; + } + + std::unique_ptr CANMessageDataCallback::copy_if_not_owned(std::unique_ptr self) const + { + // A callback doesn't own it's data, but it does own the callback function, so we can just return itself. + return self; + } + +} // namespace isobus \ No newline at end of file diff --git a/isobus/src/can_network_configuration.cpp b/isobus/src/can_network_configuration.cpp index b088b6f7..9dcea874 100644 --- a/isobus/src/can_network_configuration.cpp +++ b/isobus/src/can_network_configuration.cpp @@ -57,4 +57,14 @@ namespace isobus { return networkManagerMaxFramesToSendPerUpdate; } + + void CANNetworkConfiguration::set_number_of_packets_per_cts_message(std::uint8_t numberFrames) + { + numberOfPacketsPerCTSMessage = numberFrames; + } + + std::uint8_t CANNetworkConfiguration::get_number_of_packets_per_cts_message() const + { + return numberOfPacketsPerCTSMessage; + } } diff --git a/isobus/src/can_network_manager.cpp b/isobus/src/can_network_manager.cpp index 308475d7..f22cbfc2 100644 --- a/isobus/src/can_network_manager.cpp +++ b/isobus/src/can_network_manager.cpp @@ -33,7 +33,6 @@ namespace isobus { receiveMessageList.clear(); initialized = true; - transportProtocol.initialize({}); extendedTransportProtocol.initialize({}); } @@ -132,25 +131,47 @@ namespace isobus ((parameterGroupNumber == static_cast(CANLibParameterGroupNumber::AddressClaim)) || (sourceControlFunction->get_address_valid()))) { - CANLibProtocol *currentProtocol; - - // See if any transport layer protocol can handle this message - for (std::uint32_t i = 0; i < CANLibProtocol::get_number_protocols(); i++) + std::unique_ptr messageData; + if (nullptr != frameChunkCallback) + { + messageData.reset(new CANMessageDataCallback(dataLength, frameChunkCallback, parentPointer)); + } + else + { + messageData.reset(new CANMessageDataView(dataBuffer, dataLength)); + } + if (transportProtocol.protocol_transmit_message(parameterGroupNumber, + messageData, + sourceControlFunction, + destinationControlFunction, + transmitCompleteCallback, + parentPointer)) + { + // Successfully sent via the transport protocol + retVal = true; + } + else { - if (CANLibProtocol::get_protocol(i, currentProtocol)) + //! @todo convert the other protocols to stop using the abstract protocollib class + CANLibProtocol *currentProtocol; + // See if any transport layer protocol can handle this message + for (std::uint32_t i = 0; i < CANLibProtocol::get_number_protocols(); i++) { - retVal = currentProtocol->protocol_transmit_message(parameterGroupNumber, - dataBuffer, - dataLength, - sourceControlFunction, - destinationControlFunction, - transmitCompleteCallback, - parentPointer, - frameChunkCallback); - - if (retVal) + if (CANLibProtocol::get_protocol(i, currentProtocol)) { - break; + retVal = currentProtocol->protocol_transmit_message(parameterGroupNumber, + dataBuffer, + dataLength, + sourceControlFunction, + destinationControlFunction, + transmitCompleteCallback, + parentPointer, + frameChunkCallback); + + if (retVal) + { + break; + } } } } @@ -211,6 +232,9 @@ namespace isobus prune_inactive_control_functions(); + // Update transport protocols + transportProtocol.update(); + for (std::size_t i = 0; i < CANLibProtocol::get_number_protocols(); i++) { CANLibProtocol *currentProtocol = nullptr; @@ -459,7 +483,14 @@ namespace isobus return retVal; } - CANNetworkManager::CANNetworkManager() + CANNetworkManager::CANNetworkManager() : + transportProtocol([this](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { return this->send_can_message(parameterGroupNumber, data.begin(), data.size(), sourceControlFunction, destinationControlFunction, priority); }, + [this](const CANMessage &message) { this->protocol_message_callback(message); }, + &configuration) { currentBusloadBitAccumulator.fill(0); lastAddressClaimRequestTimestamp_ms.fill(0); @@ -991,6 +1022,7 @@ namespace isobus process_can_message_for_address_violations(currentMessage); // Update Special Callbacks, like protocols and non-cf specific ones + transportProtocol.process_message(currentMessage); process_protocol_pgn_callbacks(currentMessage); process_any_control_function_pgn_callbacks(currentMessage); @@ -1047,6 +1079,7 @@ namespace isobus void CANNetworkManager::protocol_message_callback(const CANMessage &message) { process_can_message_for_global_and_partner_callbacks(message); + process_any_control_function_pgn_callbacks(message); process_can_message_for_commanded_address(message); } diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index 84a9b280..b2c62a8c 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -4,6 +4,7 @@ /// @brief A protocol that handles the ISO11783/J1939 transport protocol. /// It handles both the broadcast version (BAM) and and the connection mode version. /// @author Adrian Del Grosso +/// @author Daan Steenbergen /// /// @copyright 2022 Adrian Del Grosso //================================================================================================ @@ -11,860 +12,1065 @@ #include "isobus/isobus/can_transport_protocol.hpp" #include "isobus/isobus/can_general_parameter_group_numbers.hpp" -#include "isobus/isobus/can_network_configuration.hpp" -#include "isobus/isobus/can_network_manager.hpp" +#include "isobus/isobus/can_internal_control_function.hpp" +#include "isobus/isobus/can_message.hpp" #include "isobus/isobus/can_stack_logger.hpp" #include "isobus/utility/system_timing.hpp" #include "isobus/utility/to_string.hpp" #include +#include namespace isobus { - TransportProtocolManager::TransportProtocolSession::TransportProtocolSession(Direction sessionDirection, std::uint8_t canPortIndex) : - sessionMessage(canPortIndex), - sessionDirection(sessionDirection) + TransportProtocolManager::TransportProtocolSession::TransportProtocolSession(Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) : + direction(direction), + parameterGroupNumber(parameterGroupNumber), + data(std::move(data)), + totalMessageSize(totalMessageSize), + source(source), + destination(destination), + totalNumberOfPackets(totalNumberOfPackets), + clearToSendPacketCountMax(clearToSendPacketMax), + sessionCompleteCallback(sessionCompleteCallback), + parent(parentPointer) { } - bool TransportProtocolManager::TransportProtocolSession::operator==(const TransportProtocolSession &obj) + bool TransportProtocolManager::TransportProtocolSession::operator==(const TransportProtocolSession &obj) const { - return ((sessionMessage.get_source_control_function() == obj.sessionMessage.get_source_control_function()) && - (sessionMessage.get_destination_control_function() == obj.sessionMessage.get_destination_control_function()) && - (sessionMessage.get_identifier().get_parameter_group_number() == obj.sessionMessage.get_identifier().get_parameter_group_number())); + return ((source == obj.source) && (destination == obj.destination) && (parameterGroupNumber == obj.parameterGroupNumber)); } - std::uint32_t TransportProtocolManager::TransportProtocolSession::get_message_data_length() const + bool TransportProtocolManager::TransportProtocolSession::matches(std::shared_ptr other_source, std::shared_ptr other_destination) const { - if (nullptr != frameChunkCallback) - { - return frameChunkCallbackMessageLength; - } - return sessionMessage.get_data_length(); + return ((source == other_source) && (destination == other_destination)); + } + + TransportProtocolManager::TransportProtocolSession::Direction TransportProtocolManager::TransportProtocolSession::get_direction() const + { + return direction; + } + + TransportProtocolManager::StateMachineState TransportProtocolManager::TransportProtocolSession::get_state() const + { + return state; + } + + std::uint32_t TransportProtocolManager::TransportProtocolSession::get_message_length() const + { + return totalMessageSize; + } + + CANMessageData &TransportProtocolManager::TransportProtocolSession::get_data() const + { + return *data; + } + + std::shared_ptr TransportProtocolManager::TransportProtocolSession::get_source() const + { + return source; + } + + std::shared_ptr TransportProtocolManager::TransportProtocolSession::get_destination() const + { + return destination; + } + + std::uint32_t TransportProtocolManager::TransportProtocolSession::get_parameter_group_number() const + { + return parameterGroupNumber; + } + + bool TransportProtocolManager::TransportProtocolSession::is_broadcast() const + { + return (nullptr == destination); } - TransportProtocolManager::~TransportProtocolManager() + TransportProtocolManager::TransportProtocolSession TransportProtocolManager::TransportProtocolSession::create_receive_session(std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination) { - // No need to clean up, as this object is a member of the network manager - // so its callbacks will be cleared at destruction time + return TransportProtocolSession(TransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + clearToSendPacketMax, + source, + destination, + nullptr, + nullptr); } - void TransportProtocolManager::initialize(CANLibBadge) + TransportProtocolManager::TransportProtocolSession TransportProtocolManager::TransportProtocolSession::create_transmit_session(std::uint32_t parameterGroupNumber, + std::unique_ptr data, + std::shared_ptr source, + std::shared_ptr destination, + std::uint8_t clearToSendPacketMax, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) { - if (!initialized) + auto totalMessageSize = static_cast(data->size()); + auto totalPacketCount = static_cast(totalMessageSize / PROTOCOL_BYTES_PER_FRAME); + if (0 != (totalMessageSize % PROTOCOL_BYTES_PER_FRAME)) { - initialized = true; - CANNetworkManager::CANNetwork.add_protocol_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), process_message, this); - CANNetworkManager::CANNetwork.add_protocol_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::TransportProtocolData), process_message, this); + totalPacketCount++; } + return TransportProtocolSession(TransportProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + totalMessageSize, + totalPacketCount, + clearToSendPacketMax, + source, + destination, + sessionCompleteCallback, + parentPointer); } - void TransportProtocolManager::process_message(const CANMessage &message) + void TransportProtocolManager::TransportProtocolSession::set_state(StateMachineState value) { - if ((nullptr != message.get_source_control_function()) && - ((nullptr == message.get_destination_control_function()) || - (nullptr != CANNetworkManager::CANNetwork.get_internal_control_function(message.get_destination_control_function())))) - { - switch (message.get_identifier().get_parameter_group_number()) - { - case static_cast(CANLibParameterGroupNumber::TransportProtocolCommand): - { - TransportProtocolSession *session; - const auto &data = message.get_data(); - const std::uint32_t pgn = (static_cast(data[5]) | (static_cast(data[6]) << 8) | (static_cast(data[7]) << 16)); - switch (data[0]) - { - case BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR: - { - if (CAN_DATA_LENGTH == message.get_data_length()) - { - if ((nullptr == message.get_destination_control_function()) && - (activeSessions.size() < CANNetworkManager::CANNetwork.get_configuration().get_max_number_transport_protocol_sessions()) && - (!get_session(session, message.get_source_control_function(), message.get_destination_control_function(), pgn))) - { - TransportProtocolSession *newSession = new TransportProtocolSession(TransportProtocolSession::Direction::Receive, message.get_can_port_index()); - CANIdentifier tempIdentifierData(CANIdentifier::Type::Extended, pgn, CANIdentifier::CANPriority::PriorityLowest7, BROADCAST_CAN_ADDRESS, message.get_source_control_function()->get_address()); - newSession->sessionMessage.set_data_size(static_cast(data[1]) | static_cast(data[2] << 8)); - newSession->sessionMessage.set_source_control_function(message.get_source_control_function()); - newSession->sessionMessage.set_destination_control_function(nullptr); - newSession->packetCount = data[3]; - newSession->sessionMessage.set_identifier(tempIdentifierData); - newSession->state = StateMachineState::RxDataSession; - newSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - activeSessions.push_back(newSession); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Debug, - "[TP]: New Rx BAM Session. Source: " + - isobus::to_string(static_cast(newSession->sessionMessage.get_source_control_function()->get_address()))); - } - else - { - // Don't send an abort, they're probably expecting a CTS so it'll timeout - // Or maybe if we already had a session they sent a second BAM? Also bad - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Can't Create an Rx BAM session"); - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Bad BAM Message Length"); - } - } - break; + state = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } - case REQUEST_TO_SEND_MULTIPLEXOR: - { - if (CAN_DATA_LENGTH == message.get_data_length()) - { - if ((nullptr != message.get_destination_control_function()) && - (activeSessions.size() < CANNetworkManager::CANNetwork.get_configuration().get_max_number_transport_protocol_sessions()) && - (!get_session(session, message.get_source_control_function(), message.get_destination_control_function(), pgn))) - { - TransportProtocolSession *newSession = new TransportProtocolSession(TransportProtocolSession::Direction::Receive, message.get_can_port_index()); - CANIdentifier tempIdentifierData(CANIdentifier::Type::Extended, pgn, CANIdentifier::CANPriority::PriorityLowest7, message.get_destination_control_function()->get_address(), message.get_source_control_function()->get_address()); - newSession->sessionMessage.set_data_size(static_cast(data[1]) | static_cast(data[2] << 8)); - newSession->sessionMessage.set_source_control_function(message.get_source_control_function()); - newSession->sessionMessage.set_destination_control_function(message.get_destination_control_function()); - newSession->packetCount = data[3]; - newSession->clearToSendPacketMax = data[4]; - newSession->sessionMessage.set_identifier(tempIdentifierData); - newSession->state = StateMachineState::ClearToSend; - newSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - activeSessions.push_back(newSession); - } - else if ((get_session(session, message.get_source_control_function(), message.get_destination_control_function(), pgn)) && - (nullptr != message.get_destination_control_function()) && - (ControlFunction::Type::Internal == message.get_destination_control_function()->get_type())) - { - abort_session(pgn, ConnectionAbortReason::AlreadyInCMSession, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Sent abort, RTS when already in CM session"); - } - else if ((activeSessions.size() >= CANNetworkManager::CANNetwork.get_configuration().get_max_number_transport_protocol_sessions()) && - (nullptr != message.get_destination_control_function()) && - (ControlFunction::Type::Internal == message.get_destination_control_function()->get_type())) - { - abort_session(pgn, ConnectionAbortReason::SystemResourcesNeeded, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Sent abort, No Sessions Available"); - } - } - else - { - // Bad RTS message length. Can't really abort? Not sure what the PGN is if length < 8 - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Received Bad Message Length for an RTS"); - } - } - break; + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_cts_number_of_packets_remaining() const + { + std::uint8_t packetsSinceCTS = get_last_packet_number() - lastAcknowledgedPacketNumber; + return clearToSendPacketCount - packetsSinceCTS; + } - case CLEAR_TO_SEND_MULTIPLEXOR: - { - // Can't happen when doing a BAM session, make sure the session type is correct - if ((CAN_DATA_LENGTH == message.get_data_length()) && - (nullptr != message.get_destination_control_function()) && - (nullptr != message.get_source_control_function())) - { - const std::uint8_t packetsToBeSent = data[1]; - - if (get_session(session, message.get_destination_control_function(), message.get_source_control_function(), pgn)) - { - if (StateMachineState::WaitForClearToSend == session->state) - { - session->packetCount = packetsToBeSent; - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - // If 0 was sent as the packet number, they want us to wait. - // Just sit here in this state until we get a non-zero packet count - if (0 != packetsToBeSent) - { - session->lastPacketNumber = 0; - session->state = StateMachineState::TxDataSession; - } - } - else - { - // The session exists, but we're probably already in the TxDataSession state. Need to abort - // In the case of Rx'ing a CTS, we're the source in the session - abort_session(pgn, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Sent abort, CTS while in data session, PGN: " + isobus::to_string(pgn)); - } - } - else - { - // We got a CTS but no session exists. Aborting clears up the situation faster than waiting for them to timeout - // In the case of Rx'ing a CTS, we're the source in the session - abort_session(pgn, ConnectionAbortReason::AnyOtherError, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Sent abort, CTS With no matching session, PGN: " + isobus::to_string(pgn)); - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Received an Invalid CTS"); - } - } - break; + void TransportProtocolManager::TransportProtocolSession::set_cts_number_of_packets(std::uint8_t value) + { + clearToSendPacketCount = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } - case END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR: - { - // Can't happen when doing a BAM session, make sure the session type is correct - if ((CAN_DATA_LENGTH == message.get_data_length()) && - (nullptr != message.get_destination_control_function()) && - (nullptr != message.get_source_control_function())) - { - if (get_session(session, message.get_destination_control_function(), message.get_source_control_function(), pgn)) - { - if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) - { - // We completed our Tx session! - session->state = StateMachineState::None; - close_session(session, true); - } - else - { - abort_session(pgn, ConnectionAbortReason::AnyOtherError, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - close_session(session, false); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Sent abort, received EOM in wrong session state, PGN: " + isobus::to_string(pgn)); - } - } - else - { - abort_session(pgn, ConnectionAbortReason::AnyOtherError, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Sent abort, received EOM without matching session, PGN: " + isobus::to_string(pgn)); - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Bad EOM received"); - } - } - break; + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_cts_number_of_packets() const + { + return clearToSendPacketCount; + } - case CONNECTION_ABORT_MULTIPLEXOR: - { - if (get_session(session, message.get_destination_control_function(), message.get_source_control_function(), pgn)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Received an abort for an session with PGN: " + isobus::to_string(pgn)); - close_session(session, false); - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Received an abort with no matching session with PGN: " + isobus::to_string(pgn)); - } - } - break; + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_rts_number_of_packet_limit() const + { + return clearToSendPacketCountMax; + } - default: - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Bad Mux in Transport Protocol Command"); - } - break; - } - } - break; + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_last_sequence_number() const + { + return lastSequenceNumber; + } - case static_cast(CANLibParameterGroupNumber::TransportProtocolData): - { - TransportProtocolSession *tempSession = nullptr; + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_last_packet_number() const + { + return lastSequenceNumber; + } - if ((CAN_DATA_LENGTH == message.get_data_length()) && - (get_session(tempSession, message.get_source_control_function(), message.get_destination_control_function())) && - (StateMachineState::RxDataSession == tempSession->state)) - { - // Check for valid sequence number - if (message.get_data()[SEQUENCE_NUMBER_DATA_INDEX] == (tempSession->lastPacketNumber + 1)) - { - for (std::uint8_t i = SEQUENCE_NUMBER_DATA_INDEX; (i < PROTOCOL_BYTES_PER_FRAME) && (static_cast((PROTOCOL_BYTES_PER_FRAME * tempSession->lastPacketNumber) + i) < tempSession->get_message_data_length()); i++) - { - std::uint16_t currentDataIndex = (PROTOCOL_BYTES_PER_FRAME * tempSession->lastPacketNumber) + i; - tempSession->sessionMessage.set_data(message.get_data()[1 + SEQUENCE_NUMBER_DATA_INDEX + i], currentDataIndex); - } - tempSession->lastPacketNumber++; - tempSession->processedPacketsThisSession++; - if ((tempSession->lastPacketNumber * PROTOCOL_BYTES_PER_FRAME) >= tempSession->get_message_data_length()) - { - // Send EOM Ack for CM sessions only - if (nullptr != tempSession->sessionMessage.get_destination_control_function()) - { - send_end_of_session_acknowledgement(tempSession); - } - CANNetworkManager::CANNetwork.process_any_control_function_pgn_callbacks(tempSession->sessionMessage); - CANNetworkManager::CANNetwork.protocol_message_callback(tempSession->sessionMessage); - close_session(tempSession, true); - } - tempSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - } - else if (message.get_data()[SEQUENCE_NUMBER_DATA_INDEX] == (tempSession->lastPacketNumber)) - { - // Sequence number is duplicate of the last one - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Aborting session due to duplciate sequence number"); - abort_session(tempSession, ConnectionAbortReason::DuplicateSequenceNumber); - close_session(tempSession, false); - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Aborting session due to bad sequence number"); - abort_session(tempSession, ConnectionAbortReason::BadSequenceNumber); - close_session(tempSession, false); - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Invalid BAM TP Data Received"); - if (get_session(tempSession, message.get_source_control_function(), message.get_destination_control_function())) - { - // If a session matches and ther was an error, get rid of the session - close_session(tempSession, false); - } - } - } - break; + void TransportProtocolManager::TransportProtocolSession::set_last_sequency_number(std::uint8_t value) + { + lastSequenceNumber = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } - default: - { - // This is not a runtime error, should never happen. - // Bad PGN passed to protocol. Check PGN registrations. - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Received an unexpected PGN"); - } - break; - } - } + void TransportProtocolManager::TransportProtocolSession::set_acknowledged_packet_number(std::uint8_t value) + { + lastAcknowledgedPacketNumber = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_number_of_remaining_packets() const + { + return totalNumberOfPackets - get_last_packet_number(); + } + + std::uint8_t TransportProtocolManager::TransportProtocolSession::get_total_number_of_packets() const + { + return totalNumberOfPackets; } - void TransportProtocolManager::process_message(const CANMessage &message, void *parent) + TransportProtocolManager::TransportProtocolManager(const SendCANFrameCallback &sendCANFrameCallback, + const CANMessageReceivedCallback &canMessageReceivedCallback, + const CANNetworkConfiguration *configuration) : + sendCANFrameCallback(sendCANFrameCallback), + canMessageReceivedCallback(canMessageReceivedCallback), + configuration(configuration) { - if (nullptr != parent) + } + + void TransportProtocolManager::process_broadcast_announce_message(const std::shared_ptr source, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t totalNumberOfPackets) + { + // The standard defines that we may not send aborts for messages with a global destination, we can only ignore them if we need to + if (activeSessions.size() >= configuration->get_max_number_transport_protocol_sessions()) { - reinterpret_cast(parent)->process_message(message); + // TODO: consider using maximum memory instead of maximum number of sessions + CANStackLogger::warn("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); + } + else + { + auto oldSession = get_session(source, nullptr); + if (nullptr != oldSession) + { + CANStackLogger::warn("[TP]: Received Broadcast Announcement Message (BAM) while a session already existed for this source (%hu), overwriting for 0x%05X...", source->get_address(), parameterGroupNumber); + close_session(*oldSession, false); + } + + auto data = std::unique_ptr(new CANMessageDataVector(totalMessageSize)); + + TransportProtocolSession newSession = TransportProtocolSession::create_receive_session(parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + 0xFF, // Arbitrary - unused for broadcast + source, + nullptr); // Global destination + newSession.set_state(StateMachineState::RxDataSession); + activeSessions.push_back(std::move(newSession)); + + CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); } } - bool TransportProtocolManager::protocol_transmit_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *dataBuffer, - std::uint32_t messageLength, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer, - DataChunkCallback frameChunkCallback) + void TransportProtocolManager::process_request_to_send(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax) { - TransportProtocolSession *session; - bool retVal = false; - - if ((messageLength <= MAX_PROTOCOL_DATA_LENGTH) && - (messageLength > CAN_DATA_LENGTH) && - ((nullptr != dataBuffer) || - (nullptr != frameChunkCallback)) && - (nullptr != source) && - (true == source->get_address_valid()) && - ((nullptr == destination) || - (destination->get_address_valid())) && - (!get_session(session, source, destination, parameterGroupNumber)) && - ((nullptr != destination) || - ((nullptr == destination) && - (!get_session(session, source, destination))))) + if (activeSessions.size() >= configuration->get_max_number_transport_protocol_sessions()) { - TransportProtocolSession *newSession = new TransportProtocolSession(TransportProtocolSession::Direction::Transmit, - source->get_can_port()); - std::uint8_t destinationAddress; + // TODO: consider using maximum memory instead of maximum number of sessions + CANStackLogger::warn("[TP]: Replying with abort to Request To Send (RTS) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); + send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::AlreadyInCMSession); + } + else + { + auto oldSession = get_session(source, destination); + if (nullptr != oldSession) + { + if (oldSession->get_parameter_group_number() != parameterGroupNumber) + { + CANStackLogger::error("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", parameterGroupNumber); + abort_session(*oldSession, ConnectionAbortReason::AlreadyInCMSession); + } + else + { + CANStackLogger::warn("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", parameterGroupNumber); + close_session(*oldSession, false); + } + } + + auto data = std::unique_ptr(new CANMessageDataVector(totalMessageSize)); - if (dataBuffer != nullptr) + if (clearToSendPacketMax > configuration->get_number_of_packets_per_cts_message()) { - newSession->sessionMessage.set_data(dataBuffer, messageLength); + CANStackLogger::debug("[TP]: Received Request To Send (RTS) with a CTS packet count of %hu, which is greater than the configured maximum of %hu, using the configured maximum instead.", clearToSendPacketMax, configuration->get_number_of_packets_per_cts_message()); + clearToSendPacketMax = configuration->get_number_of_packets_per_cts_message(); } - else + + TransportProtocolSession newSession = TransportProtocolSession::create_receive_session(parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + clearToSendPacketMax, + source, + destination); + newSession.set_state(StateMachineState::ClearToSend); + activeSessions.push_back(std::move(newSession)); + } + } + + void TransportProtocolManager::process_clear_to_send(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + std::uint8_t packetsToBeSent, + std::uint8_t nextPacketNumber) + { + auto session = get_session(destination, source); + if (nullptr != session) + { + if (session->get_parameter_group_number() != parameterGroupNumber) { - newSession->frameChunkCallback = frameChunkCallback; - newSession->frameChunkCallbackMessageLength = messageLength; + CANStackLogger::error("[TP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } - newSession->sessionMessage.set_source_control_function(source); - newSession->sessionMessage.set_destination_control_function(destination); - newSession->packetCount = (messageLength / PROTOCOL_BYTES_PER_FRAME); - newSession->lastPacketNumber = 0; - newSession->processedPacketsThisSession = 0; - newSession->sessionCompleteCallback = sessionCompleteCallback; - newSession->parent = parentPointer; - if (0 != (messageLength % PROTOCOL_BYTES_PER_FRAME)) + else if (nextPacketNumber > session->get_total_number_of_packets()) { - newSession->packetCount++; + CANStackLogger::error("[TP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::BadSequenceNumber); } - - if (nullptr != destination) + else if (StateMachineState::WaitForClearToSend != session->state) { - // CM Message - destinationAddress = destination->get_address(); - set_state(newSession, StateMachineState::RequestToSend); + // The session exists, but we're not in the right state to receive a CTS, so we must abort + CANStackLogger::warn("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else { - // BAM message - destinationAddress = BROADCAST_CAN_ADDRESS; - set_state(newSession, StateMachineState::BroadcastAnnounce); - } - - CANIdentifier messageVirtualID(CANIdentifier::Type::Extended, - parameterGroupNumber, - CANIdentifier::CANPriority::PriorityDefault6, - destinationAddress, - source->get_address()); - - newSession->sessionMessage.set_identifier(messageVirtualID); + session->set_acknowledged_packet_number(nextPacketNumber - 1); + session->set_cts_number_of_packets(packetsToBeSent); - activeSessions.push_back(newSession); - retVal = true; + // If 0 was sent as the packet number, they want us to wait. + // Just sit here in this state until we get a non-zero packet count + if (0 != packetsToBeSent) + { + session->set_state(StateMachineState::TxDataSession); + } + } } - return retVal; - } - - void TransportProtocolManager::update(CANLibBadge) - { - for (auto i : activeSessions) + else { - update_state_machine(i); + // We got a CTS but no session exists, by the standard we must ignore it + CANStackLogger::warn("[TP]: Received Clear To Send (CTS) for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); } } - bool TransportProtocolManager::abort_session(TransportProtocolSession *session, ConnectionAbortReason reason) + void TransportProtocolManager::process_end_of_session_acknowledgement(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber) { - bool retVal = false; - + auto session = get_session(destination, source); if (nullptr != session) { - std::shared_ptr myControlFunction; - std::shared_ptr partnerControlFunction; - std::array data; - std::uint32_t pgn = session->sessionMessage.get_identifier().get_parameter_group_number(); - - if (TransportProtocolSession::Direction::Transmit == session->sessionDirection) + if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) { - myControlFunction = CANNetworkManager::CANNetwork.get_internal_control_function(session->sessionMessage.get_source_control_function()); - partnerControlFunction = session->sessionMessage.get_destination_control_function(); + CANStackLogger::debug("[TP]: Completed rx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); + session->state = StateMachineState::None; + close_session(*session, true); } else { - myControlFunction = CANNetworkManager::CANNetwork.get_internal_control_function(session->sessionMessage.get_destination_control_function()); - partnerControlFunction = session->sessionMessage.get_source_control_function(); - } - - data[0] = CONNECTION_ABORT_MULTIPLEXOR; - data[1] = static_cast(reason); - data[2] = 0xFF; - data[3] = 0xFF; - data[4] = 0xFF; - data[5] = static_cast(pgn & 0xFF); - data[6] = static_cast((pgn >> 8) & 0xFF); - data[7] = static_cast((pgn >> 16) & 0xFF); - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), - data.data(), - 8, - myControlFunction, - partnerControlFunction, - CANIdentifier::CANPriority::PriorityDefault6); + // The session exists, but we're not in the right state to receive an EOM, by the standard we must ignore it + CANStackLogger::warn("[TP]: Received an End Of Message Acknowledgement message for 0x%05X, but not expecting one, ignoring.", parameterGroupNumber); + } + } + else + { + CANStackLogger::warn("[TP]: Received End Of Message Acknowledgement for 0x%05X while no session existed for this source and destination, ignoring.", parameterGroupNumber); } - return retVal; } - bool TransportProtocolManager::abort_session(std::uint32_t parameterGroupNumber, ConnectionAbortReason reason, std::shared_ptr source, std::shared_ptr destination) + void TransportProtocolManager::process_abort(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + TransportProtocolManager::ConnectionAbortReason reason) { - std::array data; + bool foundSession = false; - data[0] = CONNECTION_ABORT_MULTIPLEXOR; - data[1] = static_cast(reason); - data[2] = 0xFF; - data[3] = 0xFF; - data[4] = 0xFF; - data[5] = static_cast(parameterGroupNumber & 0xFF); - data[6] = static_cast((parameterGroupNumber >> 8) & 0xFF); - data[7] = static_cast((parameterGroupNumber >> 16) & 0xFF); - return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), - data.data(), - 8, - source, - destination, - CANIdentifier::CANPriority::PriorityDefault6); - } + auto session = get_session(source, destination); + if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) + { + foundSession = true; + CANStackLogger::error("[TP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + close_session(*session, false); + } + session = get_session(destination, source); + if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) + { + foundSession = true; + CANStackLogger::error("[TP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + close_session(*session, false); + } - void TransportProtocolManager::close_session(TransportProtocolSession *session, bool successfull) - { - if (nullptr != session) + if (!foundSession) { - process_session_complete_callback(session, successfull); - auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); - if (activeSessions.end() != sessionLocation) - { - activeSessions.erase(sessionLocation); - delete session; - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Debug, "[TP]: Session Closed"); - } + CANStackLogger::warn("[TP]: Received an abort (reason=%hu) with no matching session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); } } - void TransportProtocolManager::process_session_complete_callback(TransportProtocolSession *session, bool success) + void TransportProtocolManager::process_connection_management_message(const CANMessage &message) { - if ((nullptr != session) && - (nullptr != session->sessionCompleteCallback) && - (nullptr != session->sessionMessage.get_source_control_function()) && - (ControlFunction::Type::Internal == session->sessionMessage.get_source_control_function()->get_type())) + if (CAN_DATA_LENGTH != message.get_data_length()) { - session->sessionCompleteCallback(session->sessionMessage.get_identifier().get_parameter_group_number(), - session->get_message_data_length(), - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - success, - session->parent); + CANStackLogger::warn("[TP]: Received a Connection Management message of invalid length %hu", message.get_data_length()); + return; } - } - bool TransportProtocolManager::send_broadcast_announce_message(TransportProtocolSession *session) const - { - bool retVal = false; + const auto parameterGroupNumber = message.get_uint24_at(5); - if (nullptr != session) + switch (message.get_uint8_at(0)) { - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR, - static_cast(session->get_message_data_length() & 0xFF), - static_cast((session->get_message_data_length() >> 8) & 0xFF), - session->packetCount, - 0xFF, - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - nullptr, - CANIdentifier::CANPriority::PriorityDefault6); + case BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR: + { + if (message.is_broadcast()) + { + const auto totalMessageSize = message.get_uint16_at(1); + const auto totalNumberOfPackets = message.get_uint8_at(3); + process_broadcast_announce_message(message.get_source_control_function(), + parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets); + } + else + { + CANStackLogger::warn("[TP]: Received a Broadcast Announcement Message (BAM) with a non-global destination, ignoring"); + } + } + break; + + case REQUEST_TO_SEND_MULTIPLEXOR: + { + if (message.is_broadcast()) + { + CANStackLogger::warn("[TP]: Received a Request to Send (RTS) message with a global destination, ignoring"); + } + else + { + const auto totalMessageSize = message.get_uint16_at(1); + const auto totalNumberOfPackets = message.get_uint8_at(3); + const auto clearToSendPacketMax = message.get_uint8_at(4); + process_request_to_send(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + clearToSendPacketMax); + } + } + break; + + case CLEAR_TO_SEND_MULTIPLEXOR: + { + if (message.is_broadcast()) + { + CANStackLogger::warn("[TP]: Received a Clear to Send (CTS) message with a global destination, ignoring"); + } + else + { + const auto packetsToBeSent = message.get_uint8_at(1); + const auto nextPacketNumber = message.get_uint8_at(2); + process_clear_to_send(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + packetsToBeSent, + nextPacketNumber); + } + } + break; + + case END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR: + { + if (message.is_broadcast()) + { + CANStackLogger::warn("[TP]: Received an End of Message Acknowledge message with a global destination, ignoring"); + } + else + { + process_end_of_session_acknowledgement(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber); + } + } + break; + + case CONNECTION_ABORT_MULTIPLEXOR: + { + if (message.is_broadcast()) + { + CANStackLogger::warn("[TP]: Received an Abort message with a global destination, ignoring"); + } + else + { + const auto reason = static_cast(message.get_uint8_at(1)); + process_abort(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + reason); + } + } + break; + + default: + { + CANStackLogger::warn("[TP]: Bad Mux in Transport Protocol Connection Management message"); + } + break; } - return retVal; } - bool TransportProtocolManager::send_clear_to_send(TransportProtocolSession *session) const + void TransportProtocolManager::process_data_transfer_message(const CANMessage &message) { - bool retVal = false; + if (CAN_DATA_LENGTH != message.get_data_length()) + { + CANStackLogger::warn("[TP]: Received a Data Transfer message of invalid length %hu", message.get_data_length()); + return; + } + + auto source = message.get_source_control_function(); + auto destination = message.is_broadcast() ? nullptr : message.get_destination_control_function(); + + auto sequenceNumber = message.get_uint8_at(SEQUENCE_NUMBER_DATA_INDEX); + auto session = get_session(source, destination); if (nullptr != session) { - std::uint8_t packetsRemaining = (session->packetCount - session->processedPacketsThisSession); - std::uint8_t packetsThisSegment; - - if (session->clearToSendPacketMax < packetsRemaining) + if (StateMachineState::RxDataSession != session->state) { - packetsThisSegment = session->clearToSendPacketMax; + CANStackLogger::warn("[TP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); + abort_session(*session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); } - else + else if (sequenceNumber == session->get_last_sequence_number()) { - packetsThisSegment = packetsRemaining; + CANStackLogger::error("[TP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); + abort_session(*session, ConnectionAbortReason::DuplicateSequenceNumber); } + else if (sequenceNumber == (session->get_last_sequence_number() + 1)) + { + // Convert data type to a vector to allow for manipulation + auto &data = static_cast(session->get_data()); + + // Correct sequence number, copy the data + for (std::uint8_t i = 0; i < PROTOCOL_BYTES_PER_FRAME; i++) + { + std::uint32_t currentDataIndex = (PROTOCOL_BYTES_PER_FRAME * session->get_last_packet_number()) + i; + if (currentDataIndex < session->get_message_length()) + { + data.set_byte(currentDataIndex, message.get_uint8_at(1 + i)); + } + else + { + // Reached the end of the message, no need to copy any more data + break; + } + } - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { CLEAR_TO_SEND_MULTIPLEXOR, - packetsThisSegment, - static_cast(session->processedPacketsThisSession + 1), - 0xFF, - 0xFF, - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_destination_control_function()), - session->sessionMessage.get_source_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); + session->set_last_sequency_number(sequenceNumber); + if (session->get_number_of_remaining_packets() == 0) + { + // Send End of Message Acknowledgement for sessions with specific destination only + if (!message.is_broadcast()) + { + send_end_of_session_acknowledgement(*session); + } + else + { + CANStackLogger::debug("[TP]: Completed broadcast rx session for 0x%05X", session->get_parameter_group_number()); + } + + // Construct the completed message + CANMessage completedMessage(0); + completedMessage.set_identifier(CANIdentifier(CANIdentifier::Type::Extended, + session->get_parameter_group_number(), + CANIdentifier::CANPriority::PriorityDefault6, + session->is_broadcast() ? CANIdentifier::GLOBAL_ADDRESS : destination->get_address(), + source->get_address())); + completedMessage.set_source_control_function(source); + completedMessage.set_destination_control_function(destination); + completedMessage.set_data(data.data().begin(), static_cast(data.size())); + + canMessageReceivedCallback(completedMessage); + close_session(*session, true); + } + else if (session->get_cts_number_of_packets_remaining() == 0) + { + send_clear_to_send(*session); + } + } + else + { + CANStackLogger::error("[TP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); + abort_session(*session, ConnectionAbortReason::BadSequenceNumber); + } + } + else if (!message.is_broadcast()) + { + CANStackLogger::warn("[TP]: Received a Data Transfer message from %hu with no matching session, ignoring...", source->get_address()); } - return retVal; } - bool TransportProtocolManager::send_request_to_send(TransportProtocolSession *session) const + void TransportProtocolManager::process_message(const CANMessage &message) { - bool retVal = false; - - if (nullptr != session) + // TODO: Allow sniffing of messages to all addresses, not just the ones we normally listen to (#297) + if (message.has_valid_source_control_function() && (message.has_valid_destination_control_function() || message.is_broadcast())) { - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session->get_message_data_length() & 0xFF), - static_cast((session->get_message_data_length() >> 8) & 0xFF), - session->packetCount, - 0xFF, - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); + switch (message.get_identifier().get_parameter_group_number()) + { + case static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement): + { + process_connection_management_message(message); + } + break; + + case static_cast(CANLibParameterGroupNumber::TransportProtocolDataTransfer): + { + process_data_transfer_message(message); + } + break; + + default: + break; + } } - return retVal; } - bool TransportProtocolManager::send_end_of_session_acknowledgement(TransportProtocolSession *session) const + bool TransportProtocolManager::protocol_transmit_message(std::uint32_t parameterGroupNumber, + std::unique_ptr &data, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) { - bool retVal = false; - - if (nullptr != session) + // Return false early if we can't send the message + if ((nullptr == data) || (data->size() <= CAN_DATA_LENGTH) || (data->size() > MAX_PROTOCOL_DATA_LENGTH)) { - std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { 0 }; - std::uint32_t messageLength = session->get_message_data_length(); - std::uint32_t pgn = session->sessionMessage.get_identifier().get_parameter_group_number(); + // Invalid message length + return false; + } + else if ((nullptr == source) || (!source->get_address_valid()) || has_session(source, destination)) + { + return false; + } - dataBuffer[0] = END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR; - dataBuffer[1] = static_cast(messageLength); - dataBuffer[2] = static_cast(messageLength >> 8); - dataBuffer[3] = (static_cast((messageLength - 1) / 7) + 1); - dataBuffer[4] = 0xFF; - dataBuffer[5] = static_cast(pgn); - dataBuffer[6] = static_cast(pgn >> 8); - dataBuffer[7] = static_cast(pgn >> 16); + // We can handle this message! If we only have a view of the data, let's clone the data, + // so we don't have to worry about it being deleted. + data = data->copy_if_not_owned(std::move(data)); - // This message only needs to be sent if we're the recipient. Sanity check the destination is us - if (ControlFunction::Type::Internal == session->sessionMessage.get_destination_control_function()->get_type()) - { - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolCommand), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_destination_control_function()), - session->sessionMessage.get_source_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); - } + TransportProtocolSession session = TransportProtocolSession::create_transmit_session(parameterGroupNumber, + std::move(data), + source, + destination, + configuration->get_number_of_packets_per_cts_message(), + sessionCompleteCallback, + parentPointer); + + if (session.is_broadcast()) + { + // Broadcast message + session.set_state(StateMachineState::BroadcastAnnounce); + CANStackLogger::debug("[TP]: New broadcast tx session for 0x%05X. Source: %hu", + parameterGroupNumber, + source->get_address()); } else { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[TP]: Attempted to send EOM to null session"); + // Destination specific message + session.set_state(StateMachineState::RequestToSend); + CANStackLogger::debug("[TP]: New tx session for 0x%05X. Source: %hu, destination: %hu", + parameterGroupNumber, + source->get_address(), + destination->get_address()); } - return retVal; + activeSessions.push_back(std::move(session)); + return true; } - void TransportProtocolManager::set_state(TransportProtocolSession *session, StateMachineState value) + void TransportProtocolManager::update() { - if (nullptr != session) + // We use a fancy for loop here to allow us to remove sessions from the list while iterating + for (std::size_t i = activeSessions.size(); i > 0; i--) { - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - session->state = value; + auto &session = activeSessions.at(i - 1); + if (!session.get_source()->get_address_valid()) + { + CANStackLogger::warn("[TP]: Closing active session as the source control function is no longer valid"); + close_session(session, false); + } + else if (!session.is_broadcast() && !session.get_destination()->get_address_valid()) + { + CANStackLogger::warn("[TP]: Closing active session as the destination control function is no longer valid"); + close_session(session, false); + } + else if (StateMachineState::None != session.state) + { + update_state_machine(session); + } } } - bool TransportProtocolManager::get_session(TransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination) + void TransportProtocolManager::send_data_transfer_packets(TransportProtocolSession &session) { - session = nullptr; + std::array buffer; + std::uint32_t framesToSend = session.get_cts_number_of_packets_remaining(); + if (session.is_broadcast()) + { + framesToSend = 1; + } + else if (framesToSend > configuration->get_max_number_of_network_manager_protocol_frames_per_update()) + { + framesToSend = configuration->get_max_number_of_network_manager_protocol_frames_per_update(); + } - for (auto i : activeSessions) + // Try and send packets + for (std::uint8_t i = 0; i < framesToSend; i++) { - if ((i->sessionMessage.get_source_control_function() == source) && - (i->sessionMessage.get_destination_control_function() == destination)) + buffer[0] = session.get_last_sequence_number() + 1; + + std::uint8_t dataOffset = session.get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) { - session = i; + std::uint32_t index = dataOffset + j; + if (index < session.get_message_length()) + { + buffer[1 + j] = session.get_data().get_byte(index); + } + else + { + buffer[1 + j] = 0xFF; + } + } + + if (sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolDataTransfer), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_source()), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7)) + { + session.set_last_sequency_number(session.get_last_sequence_number() + 1); + } + else + { + // Process more next time protocol is updated break; } } - return (nullptr != session); - } - - bool TransportProtocolManager::get_session(TransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination, std::uint32_t parameterGroupNumber) - { - bool retVal = false; - session = nullptr; - if ((get_session(session, source, destination)) && - (session->sessionMessage.get_identifier().get_parameter_group_number() == parameterGroupNumber)) + if (session.get_number_of_remaining_packets() == 0) { - retVal = true; + if (session.is_broadcast()) + { + CANStackLogger::debug("[TP]: Completed broadcast tx session for 0x%05X", session.get_parameter_group_number()); + close_session(session, true); + } + else + { + session.set_state(StateMachineState::WaitForEndOfMessageAcknowledge); + } + } + else if (session.get_cts_number_of_packets_remaining() == 0) + { + session.set_state(StateMachineState::WaitForClearToSend); } - return retVal; } - void TransportProtocolManager::update_state_machine(TransportProtocolSession *session) + void TransportProtocolManager::update_state_machine(TransportProtocolSession &session) { - if (nullptr != session) + switch (session.state) { - switch (session->state) - { - case StateMachineState::None: - { - } + case StateMachineState::None: break; - case StateMachineState::ClearToSend: + case StateMachineState::ClearToSend: + { + if (send_clear_to_send(session)) { - if (send_clear_to_send(session)) - { - set_state(session, StateMachineState::RxDataSession); - } + session.set_state(StateMachineState::RxDataSession); } - break; + } + break; - case StateMachineState::WaitForClearToSend: - case StateMachineState::WaitForEndOfMessageAcknowledge: + case StateMachineState::WaitForClearToSend: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) { - if (SystemTiming::time_expired_ms(session->timestamp_ms, T2_T3_TIMEOUT_MS)) + CANStackLogger::error("[TP]: Timeout rx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); + if (session.get_cts_number_of_packets() > 0) { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: Timeout"); + // A connection is only considered established if we've received at least one CTS before + // And we can only abort a connection if it's considered established abort_session(session, ConnectionAbortReason::Timeout); + } + else + { close_session(session, false); } } - break; - - case StateMachineState::RequestToSend: + } + break; + case StateMachineState::WaitForEndOfMessageAcknowledge: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) { - if (send_request_to_send(session)) - { - set_state(session, StateMachineState::WaitForClearToSend); - } + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); + abort_session(session, ConnectionAbortReason::Timeout); } - break; + } + break; - case StateMachineState::BroadcastAnnounce: + case StateMachineState::RequestToSend: + { + if (send_request_to_send(session)) { - if (send_broadcast_announce_message(session)) - { - set_state(session, StateMachineState::TxDataSession); - } + session.set_state(StateMachineState::WaitForClearToSend); } - break; + } + break; - case StateMachineState::TxDataSession: + case StateMachineState::BroadcastAnnounce: + { + if (send_broadcast_announce_message(session)) { - bool sessionStillValid = true; + session.set_state(StateMachineState::TxDataSession); + } + } + break; - if ((nullptr != session->sessionMessage.get_destination_control_function()) || (SystemTiming::time_expired_ms(session->timestamp_ms, CANNetworkManager::CANNetwork.get_configuration().get_minimum_time_between_transport_protocol_bam_frames()))) - { - std::uint8_t dataBuffer[CAN_DATA_LENGTH]; - std::uint32_t framesSentThisUpdate = 0; - - // Try and send packets - for (std::uint8_t i = session->lastPacketNumber; i < session->packetCount; i++) - { - dataBuffer[0] = (session->processedPacketsThisSession + 1); - - if (nullptr != session->frameChunkCallback) - { - // Use the callback to get this frame's data - std::uint8_t callbackBuffer[7] = { - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF - }; - std::uint16_t numberBytesLeft = (session->get_message_data_length() - (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession)); - - if (numberBytesLeft > PROTOCOL_BYTES_PER_FRAME) - { - numberBytesLeft = PROTOCOL_BYTES_PER_FRAME; - } - - bool callbackSuccessful = session->frameChunkCallback(dataBuffer[0], (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession), numberBytesLeft, callbackBuffer, session->parent); - - if (callbackSuccessful) - { - for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) - { - dataBuffer[1 + j] = callbackBuffer[j]; - } - } - else - { - abort_session(session, ConnectionAbortReason::AnyOtherError); - close_session(session, false); - sessionStillValid = false; - break; - } - } - else - { - // Use the data buffer to get the data for this frame - for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) - { - std::uint32_t index = (j + (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession)); - if (index < session->get_message_data_length()) - { - dataBuffer[1 + j] = session->sessionMessage.get_data()[j + (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession)]; - } - else - { - dataBuffer[1 + j] = 0xFF; - } - } - } - - if (CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::TransportProtocolData), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - CANIdentifier::CANPriority::PriorityLowest7)) - { - framesSentThisUpdate++; - session->lastPacketNumber++; - session->processedPacketsThisSession++; - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - - if (nullptr == session->sessionMessage.get_destination_control_function()) - { - // Need to wait for the frame delay time before continuing BAM session - break; - } - else if (framesSentThisUpdate >= CANNetworkManager::CANNetwork.get_configuration().get_max_number_of_network_manager_protocol_frames_per_update()) - { - break; // Throttle the session - } - } - else - { - // Process more next time protocol is updated - break; - } - } - } + case StateMachineState::TxDataSession: + { + if (session.is_broadcast() && (!SystemTiming::time_expired_ms(session.timestamp_ms, configuration->get_minimum_time_between_transport_protocol_bam_frames()))) + { + // Need to wait before sending the next data frame of the broadcast session + } + else + { + send_data_transfer_packets(session); + } + } + break; - if (sessionStillValid) + case StateMachineState::RxDataSession: + { + if (session.is_broadcast()) + { + // Broadcast message timeout check + if (SystemTiming::time_expired_ms(session.timestamp_ms, T1_TIMEOUT_MS)) { - if ((session->lastPacketNumber == (session->packetCount)) && - (session->get_message_data_length() <= (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession))) - { - if (nullptr == session->sessionMessage.get_destination_control_function()) - { - // BAM is complete - close_session(session, true); - } - else - { - set_state(session, StateMachineState::WaitForEndOfMessageAcknowledge); - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - } - } - else if (session->lastPacketNumber == session->packetCount) - { - set_state(session, StateMachineState::WaitForClearToSend); - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - } + CANStackLogger::warn("[TP]: Broadcast rx session timeout"); + close_session(session, false); } } - break; - - case StateMachineState::RxDataSession: + else if (session.get_cts_number_of_packets_remaining() == session.get_cts_number_of_packets()) { - if (nullptr == session->sessionMessage.get_destination_control_function()) + // Waiting to receive the first data frame after CTS + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) { - // BAM Timeout check - if (SystemTiming::time_expired_ms(session->timestamp_ms, T1_TIMEOUT_MS)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: BAM Rx Timeout"); - close_session(session, false); - } + CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected first data frame)"); + abort_session(session, ConnectionAbortReason::Timeout); } - else + } + else + { + // Waiting on sequencial data frames + if (SystemTiming::time_expired_ms(session.timestamp_ms, T1_TIMEOUT_MS)) { - // CM TP Timeout check - if (SystemTiming::time_expired_ms(session->timestamp_ms, MESSAGE_TR_TIMEOUT_MS)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[TP]: CM Rx Timeout"); - abort_session(session, ConnectionAbortReason::Timeout); - close_session(session, false); - } + CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); + abort_session(session, ConnectionAbortReason::Timeout); } } - break; + } + break; + } + } + + bool TransportProtocolManager::abort_session(const TransportProtocolSession &session, ConnectionAbortReason reason) + { + bool retVal = false; + std::shared_ptr myControlFunction; + std::shared_ptr partnerControlFunction; + if (TransportProtocolSession::Direction::Transmit == session.get_direction()) + { + myControlFunction = std::static_pointer_cast(session.get_source()); + partnerControlFunction = session.get_destination(); + } + else + { + myControlFunction = std::static_pointer_cast(session.get_destination()); + partnerControlFunction = session.get_source(); + } + + if ((nullptr != myControlFunction) && (nullptr != partnerControlFunction)) + { + retVal = send_abort(myControlFunction, partnerControlFunction, session.get_parameter_group_number(), reason); + } + close_session(session, false); + return retVal; + } + + bool TransportProtocolManager::send_abort(std::shared_ptr sender, + std::shared_ptr receiver, + std::uint32_t parameterGroupNumber, + ConnectionAbortReason reason) const + { + const std::array buffer{ + CONNECTION_ABORT_MULTIPLEXOR, + static_cast(reason), + 0xFF, + 0xFF, + 0xFF, + static_cast(parameterGroupNumber & 0xFF), + static_cast((parameterGroupNumber >> 8) & 0xFF), + static_cast((parameterGroupNumber >> 16) & 0xFF) + }; + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + sender, + receiver, + CANIdentifier::CANPriority::PriorityLowest7); + } + + void TransportProtocolManager::close_session(const TransportProtocolSession &session, bool successful) + { + if ((nullptr != session.sessionCompleteCallback) && (TransportProtocolSession::Direction::Transmit == session.get_direction())) + { + if (auto source = session.get_source()) + { + session.sessionCompleteCallback(session.get_parameter_group_number(), + session.get_message_length(), + std::static_pointer_cast(source), + session.get_destination(), + successful, + session.parent); + } + } + + auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); + if (activeSessions.end() != sessionLocation) + { + activeSessions.erase(sessionLocation); + CANStackLogger::debug("[TP]: Session Closed"); + } + } + + bool TransportProtocolManager::send_broadcast_announce_message(const TransportProtocolSession &session) const + { + bool retVal = false; + if (auto source = session.get_source()) + { + const std::array buffer{ + BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + session.get_total_number_of_packets(), + 0xFF, + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(source), + nullptr, + CANIdentifier::CANPriority::PriorityLowest7); + } + return retVal; + } + + bool TransportProtocolManager::send_clear_to_send(TransportProtocolSession &session) const + { + bool retVal = false; + // Since we're the receiving side, we are the destination of the session + if (auto ourControlFunction = session.get_destination()) + { + std::uint8_t packetsThisSegment = session.get_number_of_remaining_packets(); + if (packetsThisSegment > session.get_rts_number_of_packet_limit()) + { + packetsThisSegment = session.get_rts_number_of_packet_limit(); + } + else if (packetsThisSegment > 16) + { + //! @todo apply CTS number of packets recommendation of 16 via a configuration option + packetsThisSegment = 16; + } + + const std::array buffer{ + CLEAR_TO_SEND_MULTIPLEXOR, + packetsThisSegment, + static_cast(session.get_last_packet_number() + 1), + 0xFF, + 0xFF, + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(ourControlFunction), + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + if (retVal) + { + session.set_cts_number_of_packets(packetsThisSegment); + session.set_acknowledged_packet_number(session.get_last_packet_number()); } } + return retVal; } + bool TransportProtocolManager::send_request_to_send(const TransportProtocolSession &session) const + { + bool retVal = false; + if (auto source = session.get_source()) + { + const std::array buffer{ + REQUEST_TO_SEND_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + session.get_total_number_of_packets(), + session.get_rts_number_of_packet_limit(), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(source), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7); + } + return retVal; + } + + bool TransportProtocolManager::send_end_of_session_acknowledgement(const TransportProtocolSession &session) const + { + bool retVal = false; + // Since we're the receiving side, we are the destination of the session + if (auto ourControlFunction = session.get_destination()) + { + std::uint32_t messageLength = session.get_message_length(); + std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); + + const std::array buffer{ + END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, + static_cast(messageLength & 0xFF), + static_cast((messageLength >> 8) & 0xFF), + session.get_total_number_of_packets(), + 0xFF, + static_cast(parameterGroupNumber & 0xFF), + static_cast((parameterGroupNumber >> 8) & 0xFF), + static_cast((parameterGroupNumber >> 16) & 0xFF), + }; + + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(ourControlFunction), + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + } + else + { + CANStackLogger::warn("[TP]: Attempted to send EOM to null session"); + } + return retVal; + } + + bool TransportProtocolManager::has_session(std::shared_ptr source, std::shared_ptr destination) + { + return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const TransportProtocolSession &session) { + return session.matches(source, destination); + }); + } + + TransportProtocolManager::TransportProtocolSession *TransportProtocolManager::get_session(std::shared_ptr source, + std::shared_ptr destination) + { + auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const TransportProtocolSession &session) { + return session.matches(source, destination); + }); + // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored + return (activeSessions.end() != result) ? &(*result) : nullptr; + } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 18f2e7bc..953e4c01 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,9 +36,10 @@ set(TEST_INCLUDE helpers/control_function_helpers.hpp # Set test source files set(TEST_SRC + core_network_management_tests.cpp identifier_tests.cpp + transport_protocol_tests.cpp diagnostic_protocol_tests.cpp - core_network_management_tests.cpp virtual_can_plugin_tests.cpp address_claim_tests.cpp can_name_tests.cpp diff --git a/test/diagnostic_protocol_tests.cpp b/test/diagnostic_protocol_tests.cpp index b82712e4..aa8ed38d 100644 --- a/test/diagnostic_protocol_tests.cpp +++ b/test/diagnostic_protocol_tests.cpp @@ -90,7 +90,7 @@ TEST(DIAGNOSTIC_PROTOCOL_TESTS, MessageEncoding) // Broadcast Announce Message EXPECT_EQ(CAN_DATA_LENGTH, testFrame.dataLength); - EXPECT_EQ(0x18ECFFAA, testFrame.identifier); // BAM from address AA + EXPECT_EQ(0x1CECFFAA, testFrame.identifier); // BAM from address AA EXPECT_EQ(0x20, testFrame.data[0]); // BAM Multiplexer EXPECT_EQ(expectedBAMLength & 0xFF, testFrame.data[1]); // Length LSB EXPECT_EQ((expectedBAMLength >> 8) & 0xFF, testFrame.data[2]); // Length MSB @@ -243,7 +243,7 @@ TEST(DIAGNOSTIC_PROTOCOL_TESTS, MessageEncoding) // Broadcast Announce Message EXPECT_EQ(CAN_DATA_LENGTH, testFrame.dataLength); - EXPECT_EQ(0x18ECFFAA, testFrame.identifier); // BAM from address AA + EXPECT_EQ(0x1CECFFAA, testFrame.identifier); // BAM from address AA EXPECT_EQ(0x20, testFrame.data[0]); // BAM Multiplexer EXPECT_EQ(expectedBAMLength & 0xFF, testFrame.data[1]); // Length LSB EXPECT_EQ((expectedBAMLength >> 8) & 0xFF, testFrame.data[2]); // Length MSB @@ -399,7 +399,7 @@ TEST(DIAGNOSTIC_PROTOCOL_TESTS, MessageEncoding) // Broadcast Announce Message EXPECT_EQ(CAN_DATA_LENGTH, testFrame.dataLength); - EXPECT_EQ(0x18ECFFAA, testFrame.identifier); // BAM from address AA + EXPECT_EQ(0x1CECFFAA, testFrame.identifier); // BAM from address AA EXPECT_EQ(0x20, testFrame.data[0]); // BAM Multiplexer EXPECT_EQ(expectedBAMLength & 0xFF, testFrame.data[1]); // Length LSB EXPECT_EQ((expectedBAMLength >> 8) & 0xFF, testFrame.data[2]); // Length MSB @@ -544,7 +544,7 @@ TEST(DIAGNOSTIC_PROTOCOL_TESTS, MessageEncoding) // Broadcast Announce Message EXPECT_EQ(CAN_DATA_LENGTH, testFrame.dataLength); - EXPECT_EQ(0x18ECFFAA, testFrame.identifier); // BAM from address AA + EXPECT_EQ(0x1CECFFAA, testFrame.identifier); // BAM from address AA EXPECT_EQ(0x20, testFrame.data[0]); // BAM Multiplexer EXPECT_EQ(expectedBAMLength & 0xFF, testFrame.data[1]); // Length LSB EXPECT_EQ((expectedBAMLength >> 8) & 0xFF, testFrame.data[2]); // Length MSB @@ -734,7 +734,7 @@ TEST(DIAGNOSTIC_PROTOCOL_TESTS, MessageEncoding) // Broadcast Announce Message EXPECT_EQ(CAN_DATA_LENGTH, testFrame.dataLength); - EXPECT_EQ(0x18ECFFAA, testFrame.identifier); // BAM from address AA + EXPECT_EQ(0x1CECFFAA, testFrame.identifier); // BAM from address AA EXPECT_EQ(0x20, testFrame.data[0]); // BAM Multiplexer EXPECT_EQ(expectedBAMLength & 0xFF, testFrame.data[1]); // Length LSB EXPECT_EQ((expectedBAMLength >> 8) & 0xFF, testFrame.data[2]); // Length MSB @@ -793,7 +793,7 @@ TEST(DIAGNOSTIC_PROTOCOL_TESTS, MessageEncoding) // Broadcast Announce Message EXPECT_EQ(CAN_DATA_LENGTH, testFrame.dataLength); - EXPECT_EQ(0x18ECFFAA, testFrame.identifier); // BAM from address AA + EXPECT_EQ(0x1CECFFAA, testFrame.identifier); // BAM from address AA EXPECT_EQ(0x20, testFrame.data[0]); // BAM Multiplexer EXPECT_EQ(expectedBAMLength & 0xFF, testFrame.data[1]); // Length LSB EXPECT_EQ((expectedBAMLength >> 8) & 0xFF, testFrame.data[2]); // Length MSB diff --git a/test/transport_protocol_tests.cpp b/test/transport_protocol_tests.cpp new file mode 100644 index 00000000..416b0c09 --- /dev/null +++ b/test/transport_protocol_tests.cpp @@ -0,0 +1,2141 @@ +#include + +#include "isobus/isobus/can_transport_protocol.hpp" +#include "isobus/utility/system_timing.hpp" + +#include "helpers/control_function_helpers.hpp" +#include "helpers/messaging_helpers.hpp" + +#include +#include +#include +#include +#include + +using namespace isobus; + +// Test case for receiving a broadcast message +TEST(TRANSPORT_PROTOCOL_TESTS, BroadcastMessageReceiving) +{ + constexpr std::uint32_t pgnToReceive = 0xFEEC; + constexpr std::array dataToReceive = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + + std::uint8_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &message) { + CANIdentifier identifier = message.get_identifier(); + EXPECT_EQ(identifier.get_parameter_group_number(), pgnToReceive); + EXPECT_EQ(identifier.get_priority(), CANIdentifier::CANPriority::PriorityDefault6); + EXPECT_EQ(message.get_source_control_function(), originator); + EXPECT_TRUE(message.is_broadcast()); + EXPECT_EQ(message.get_data_length(), dataToReceive.size()); + for (std::size_t i = 0; i < dataToReceive.size(); i++) + { + EXPECT_EQ(message.get_data()[i], dataToReceive[i]); + } + messageCount++; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(nullptr, receiveMessageCallback, &defaultConfiguration); + + // Receive broadcast announcement message (BAM) + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEC00, // Transport Protocol Connection Management + originator, + { + 32, // BAM Mux + 17, // Data Length + 0, // Data Length MSB + 3, // Packet count + 0xFF, // Reserved + 0xEC, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + EXPECT_TRUE(manager.has_session(originator, nullptr)); + + // Receive the first data frame + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEB00, // Transport Protocol Data Transfer + originator, + { + 1, // Sequence number + dataToReceive[0], + dataToReceive[1], + dataToReceive[2], + dataToReceive[3], + dataToReceive[4], + dataToReceive[5], + dataToReceive[6], + })); + + // Receive the second data frame + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEB00, // Transport Protocol Data Transfer + originator, + { + 2, // Sequence number + dataToReceive[7], + dataToReceive[8], + dataToReceive[9], + dataToReceive[10], + dataToReceive[11], + dataToReceive[12], + dataToReceive[13], + })); + + // Receive the third data frame + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEB00, // Transport Protocol Data Transfer + originator, + { + 3, // Sequence number + dataToReceive[14], + dataToReceive[15], + dataToReceive[16], + 0xFF, + 0xFF, + 0xFF, + 0xFF, + })); + + // We now expect the message to be received + ASSERT_EQ(messageCount, 1); + + // After the transmission is finished, the session should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, nullptr)); +} + +// Test case for timeout when receiving broadcast message +TEST(TRANSPORT_PROTOCOL_TESTS, BroadcastMessageTimeout) +{ + auto originator = test_helpers::create_mock_control_function(0x01); + + std::uint8_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &) { + messageCount++; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(nullptr, receiveMessageCallback, &defaultConfiguration); + + // Receive broadcast announcement message (BAM) + std::uint32_t sessionUpdateTime = SystemTiming::get_timestamp_ms(); + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEC00, // Transport Protocol Connection Management + originator, + { + 32, // BAM Mux + 17, // Data Length + 0, // Data Length MSB + 3, // Packet count + 0xFF, // Reserved + 0xEC, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + EXPECT_TRUE(manager.has_session(originator, nullptr)); + + // We expect the session to exists for T1=750ms before timing out + std::uint32_t sessionRemovalTime = 0; + while (SystemTiming::get_time_elapsed_ms(sessionUpdateTime) < 1000) + { + manager.update(); + if (!manager.has_session(originator, nullptr)) + { + sessionRemovalTime = SystemTiming::get_timestamp_ms(); + break; + } + } + EXPECT_EQ(messageCount, 0); + EXPECT_NEAR(sessionRemovalTime - sessionUpdateTime, 750, 5); + + // After the transmission is finished, the session should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, nullptr)); + + // Now when we try again but stop after the first data frame, we expect the session to also exists for T1=750ms before timing out + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEC00, // Transport Protocol Connection Management + originator, + { + 32, // BAM Mux + 17, // Data Length + 0, // Data Length MSB + 3, // Packet count + 0xFF, // Reserved + 0xEC, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + ASSERT_TRUE(manager.has_session(originator, nullptr)); + + // Receive the first data frame + sessionUpdateTime = SystemTiming::get_timestamp_ms(); + manager.process_message(test_helpers::create_message_broadcast( + 7, + 0xEB00, // Transport Protocol Data Transfer + originator, + { + 1, // Sequence number + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + })); + + ASSERT_TRUE(manager.has_session(originator, nullptr)); + + // We expect the session to exists for T1=750ms before timing out + sessionRemovalTime = 0; + while (SystemTiming::get_time_elapsed_ms(sessionUpdateTime) < 1000) + { + manager.update(); + if (!manager.has_session(originator, nullptr)) + { + sessionRemovalTime = SystemTiming::get_timestamp_ms(); + break; + } + } + + EXPECT_EQ(messageCount, 0); + EXPECT_NEAR(sessionRemovalTime - sessionUpdateTime, 750, 5); + + // After the transmission is finished, the session should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, nullptr)); +}; + +// Test case for multiple concurrent broadcast messages +TEST(TRANSPORT_PROTOCOL_TESTS, BroadcastConcurrentMessaging) +{ + // We setup five sources, two of them sending the same PGN and data, and the other three sending the different PGNs and data combinations + constexpr std::uint32_t pgn1ToReceive = 0xFEEC; + constexpr std::uint32_t pgn2ToReceive = 0xFEEB; + constexpr std::array dataToReceive1 = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + constexpr std::array dataToReceive2 = { 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }; + + auto originator1 = test_helpers::create_mock_control_function(0x01); + auto originator2 = test_helpers::create_mock_control_function(0x02); + auto originator3 = test_helpers::create_mock_control_function(0x03); + auto originator4 = test_helpers::create_mock_control_function(0x04); + auto originator5 = test_helpers::create_mock_control_function(0x05); + + std::array messagesReceived = { false }; + auto receiveMessageCallback = [&](const CANMessage &message) { + CANIdentifier identifier = message.get_identifier(); + ASSERT_EQ(identifier.get_priority(), CANIdentifier::CANPriority::PriorityDefault6); + ASSERT_TRUE(message.is_broadcast()); + + std::uint32_t pgnToCheck; + const std::uint8_t *dataToCheck; + std::size_t dataLengthToCheck; + + if (message.get_source_control_function() == originator1) + { + pgnToCheck = pgn1ToReceive; + dataToCheck = dataToReceive1.data(); + dataLengthToCheck = dataToReceive1.size(); + messagesReceived[0] = true; + } + else if (message.get_source_control_function() == originator2) + { + pgnToCheck = pgn1ToReceive; + dataToCheck = dataToReceive1.data(); + dataLengthToCheck = dataToReceive1.size(); + messagesReceived[1] = true; + } + else if (message.get_source_control_function() == originator3) + { + pgnToCheck = pgn1ToReceive; + dataToCheck = dataToReceive2.data(); + dataLengthToCheck = dataToReceive2.size(); + messagesReceived[2] = true; + } + else if (message.get_source_control_function() == originator4) + { + pgnToCheck = pgn2ToReceive; + dataToCheck = dataToReceive1.data(); + dataLengthToCheck = dataToReceive1.size(); + messagesReceived[3] = true; + } + else if (message.get_source_control_function() == originator5) + { + pgnToCheck = pgn2ToReceive; + dataToCheck = dataToReceive2.data(); + dataLengthToCheck = dataToReceive2.size(); + messagesReceived[4] = true; + } + else + { + // Unexpected source, fail the test + ASSERT_TRUE(false); + } + + ASSERT_EQ(identifier.get_parameter_group_number(), pgnToCheck); + ASSERT_EQ(message.get_data_length(), dataLengthToCheck); + for (std::size_t i = 0; i < dataLengthToCheck; i++) + { + ASSERT_EQ(message.get_data()[i], dataToCheck[i]); + } + }; + + // Create the receiving transport protocol manager + CANNetworkConfiguration configuration; + configuration.set_max_number_transport_protocol_sessions(5); // We need to increase the number of sessions to 5 for this test + TransportProtocolManager rxManager(nullptr, receiveMessageCallback, &configuration); + + // Create the sending transport protocol manager + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(destinationControlFunction, nullptr); + CANMessage message(0); //! TODO: hack for now, will be fixed when we remove CANNetwork Singleton + std::uint32_t identifier = test_helpers::create_ext_can_id_broadcast(static_cast(priority), + parameterGroupNumber, + sourceControlFunction); + message.set_identifier(CANIdentifier(identifier)); + message.set_source_control_function(sourceControlFunction); + message.set_data(data.begin(), data.size()); + rxManager.process_message(message); + return true; + }; + TransportProtocolManager txManager(sendFrameCallback, nullptr, &configuration); + + // Send the messages + auto data = std::unique_ptr(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, originator1, nullptr, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, originator2, nullptr, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive2.data(), dataToReceive2.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, originator3, nullptr, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn2ToReceive, data, originator4, nullptr, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive2.data(), dataToReceive2.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn2ToReceive, data, originator5, nullptr, nullptr, nullptr)); + + ASSERT_TRUE(txManager.has_session(originator1, nullptr)); + ASSERT_TRUE(txManager.has_session(originator2, nullptr)); + ASSERT_TRUE(txManager.has_session(originator3, nullptr)); + ASSERT_TRUE(txManager.has_session(originator4, nullptr)); + ASSERT_TRUE(txManager.has_session(originator5, nullptr)); + + // Wait for the transmissions to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while (std::any_of(messagesReceived.begin(), messagesReceived.end(), [](bool received) { return !received; }) && + (SystemTiming::get_time_elapsed_ms(time) < 5 * 200)) + { + txManager.update(); + rxManager.update(); + } + + ASSERT_FALSE(rxManager.has_session(originator1, nullptr)); + ASSERT_FALSE(rxManager.has_session(originator2, nullptr)); + ASSERT_FALSE(rxManager.has_session(originator3, nullptr)); + ASSERT_FALSE(rxManager.has_session(originator4, nullptr)); + ASSERT_FALSE(rxManager.has_session(originator5, nullptr)); + ASSERT_FALSE(txManager.has_session(originator1, nullptr)); + ASSERT_FALSE(txManager.has_session(originator2, nullptr)); + ASSERT_FALSE(txManager.has_session(originator3, nullptr)); + ASSERT_FALSE(txManager.has_session(originator4, nullptr)); + ASSERT_FALSE(txManager.has_session(originator5, nullptr)); + ASSERT_TRUE(std::all_of(messagesReceived.begin(), messagesReceived.end(), [](bool received) { return received; })); +} + +// Test case for sending a destination specific message +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMessageSending) +{ + constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + std::deque responseQueue; + + std::size_t frameCount = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, originator); + EXPECT_EQ(destinationControlFunction, receiver); + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + switch (frameCount) + { + case 0: + // First we expect a Request to Send (RTS) message + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 16); // RTS control byte + EXPECT_EQ(data[1], 23); + EXPECT_EQ(data[2], 0); + EXPECT_EQ(data[3], 4); // Number of packets + EXPECT_EQ(data[4], 1); // Limit number of packets in CTS as per configuration + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + + // We respond with a clear to send (CTS) message + responseQueue.push_back(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + sourceControlFunction, + destinationControlFunction, + { + 17, // CTS Mux + 2, // Number of packets (ignores the limit in the RTS message) + 1, // Next packet to send + 0xFF, // Reserved + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + break; + + case 1: + // Then we expect the first data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 1); // Sequence number + EXPECT_EQ(data[1], dataToSent[0]); + EXPECT_EQ(data[2], dataToSent[1]); + EXPECT_EQ(data[3], dataToSent[2]); + EXPECT_EQ(data[4], dataToSent[3]); + EXPECT_EQ(data[5], dataToSent[4]); + EXPECT_EQ(data[6], dataToSent[5]); + EXPECT_EQ(data[7], dataToSent[6]); + break; + + case 2: + // Then we expect the second data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 2); // Sequence number + EXPECT_EQ(data[1], dataToSent[7]); + EXPECT_EQ(data[2], dataToSent[8]); + EXPECT_EQ(data[3], dataToSent[9]); + EXPECT_EQ(data[4], dataToSent[10]); + EXPECT_EQ(data[5], dataToSent[11]); + EXPECT_EQ(data[6], dataToSent[12]); + EXPECT_EQ(data[7], dataToSent[13]); + + // We respond with another clear to send (CTS) message + responseQueue.push_back(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + sourceControlFunction, + destinationControlFunction, + { + 17, // CTS Mux + 2, // Number of packets + 3, // Next packet to send + 0xFF, // Reserved + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + break; + + case 3: + // Then we expect the third data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 3); // Sequence number + EXPECT_EQ(data[1], dataToSent[14]); + EXPECT_EQ(data[2], dataToSent[15]); + EXPECT_EQ(data[3], dataToSent[16]); + EXPECT_EQ(data[4], dataToSent[17]); + EXPECT_EQ(data[5], dataToSent[18]); + EXPECT_EQ(data[6], dataToSent[19]); + EXPECT_EQ(data[7], dataToSent[20]); + break; + + case 4: + // Then we expect the fourth data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 4); // Sequence number + EXPECT_EQ(data[1], dataToSent[21]); + EXPECT_EQ(data[2], dataToSent[22]); + EXPECT_EQ(data[3], 0xFF); + EXPECT_EQ(data[4], 0xFF); + EXPECT_EQ(data[5], 0xFF); + EXPECT_EQ(data[6], 0xFF); + EXPECT_EQ(data[7], 0xFF); + + // We respond with a end of message acknowledge (EOMA) message + responseQueue.push_back(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + sourceControlFunction, + destinationControlFunction, + { + 19, // EOMA Mux + 23, // Data Length + 0, // Data Length MSB + 4, // Number of packets + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + break; + + default: + EXPECT_TRUE(false); + } + + frameCount++; + return true; + }; + + // Create the transport protocol manager + // We ask the originator to send only one packet per CTS message, but then we simulate it ignoring the request in the CTS message + // to test the manager compliance with the receiving control function's CTS limit. + CANNetworkConfiguration configuration; + configuration.set_number_of_packets_per_cts_message(1); + TransportProtocolManager manager(sendFrameCallback, nullptr, &configuration); + + // Send the message + auto data = std::unique_ptr(new CANMessageDataView(dataToSent.data(), dataToSent.size())); + ASSERT_TRUE(manager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(manager.has_session(originator, receiver)); + // We shouldn't be able to transmit another message + data.reset(new CANMessageDataView(dataToSent.data(), dataToSent.size())); // Data get's moved, so we need to create a new one + ASSERT_FALSE(manager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + // Also not a message with a different PGN + data.reset(new CANMessageDataView(dataToSent.data(), dataToSent.size())); // Data get's moved, so we need to create a new one + ASSERT_FALSE(manager.protocol_transmit_message(0xFEEC, data, originator, receiver, nullptr, nullptr)); + + // Wait for the transmission to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((!responseQueue.empty()) || ((frameCount < 5) && (SystemTiming::get_time_elapsed_ms(time) < 1250 + 200 + 200 + 1250 + 200 + 200 + 1250))) // maximum time for 4 packets with 2 CTS according to ISO 11783-3 + { + if (!responseQueue.empty()) + { + manager.process_message(responseQueue.front()); + responseQueue.pop_front(); + } + manager.update(); + } + + ASSERT_EQ(frameCount, 5); + + // After the transmission is finished, the session should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, receiver)); +} +// Test case for sending a broadcast message +TEST(TRANSPORT_PROTOCOL_TESTS, BroadcastMessageSending) +{ + constexpr std::uint32_t pgnToSent = 0xFEEC; + constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + + std::size_t frameCount = 0; + std::uint32_t frameTime = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, originator); + EXPECT_EQ(destinationControlFunction, nullptr); + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + switch (frameCount) + { + case 0: + // First we expect broadcast announcement message (BAM) + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 32); // BAM control byte + EXPECT_EQ(data[1], 17); // Data Length + EXPECT_EQ(data[2], 0); // Data Length MSB + EXPECT_EQ(data[3], 3); // Number of packets + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEC); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + case 1: + // Then we expect the first data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 1); // Sequence number + EXPECT_EQ(data[1], dataToSent[0]); + EXPECT_EQ(data[2], dataToSent[1]); + EXPECT_EQ(data[3], dataToSent[2]); + EXPECT_EQ(data[4], dataToSent[3]); + EXPECT_EQ(data[5], dataToSent[4]); + EXPECT_EQ(data[6], dataToSent[5]); + EXPECT_EQ(data[7], dataToSent[6]); + EXPECT_NEAR(SystemTiming::get_time_elapsed_ms(frameTime), 50, 5); // We expect the first frame to be sent after 50ms (default = J1939 requirement) + break; + + case 2: + // Then we expect the second data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 2); // Sequence number + EXPECT_EQ(data[1], dataToSent[7]); + EXPECT_EQ(data[2], dataToSent[8]); + EXPECT_EQ(data[3], dataToSent[9]); + EXPECT_EQ(data[4], dataToSent[10]); + EXPECT_EQ(data[5], dataToSent[11]); + EXPECT_EQ(data[6], dataToSent[12]); + EXPECT_EQ(data[7], dataToSent[13]); + EXPECT_NEAR(SystemTiming::get_time_elapsed_ms(frameTime), 50, 5); // We expect the time between frames to be 50ms (default = J1939 requirement) + break; + + case 3: + // Then we expect the third data frame + EXPECT_EQ(parameterGroupNumber, 0xEB00); + EXPECT_EQ(data[0], 3); // Sequence number + EXPECT_EQ(data[1], dataToSent[14]); + EXPECT_EQ(data[2], dataToSent[15]); + EXPECT_EQ(data[3], dataToSent[16]); + EXPECT_EQ(data[4], 0xFF); + EXPECT_EQ(data[5], 0xFF); + EXPECT_EQ(data[6], 0xFF); + EXPECT_EQ(data[7], 0xFF); + EXPECT_NEAR(SystemTiming::get_time_elapsed_ms(frameTime), 50, 5); // We expect the time between frames to be 50ms (default = J1939 requirement) + break; + + default: + EXPECT_TRUE(false); + } + + frameCount++; + frameTime = SystemTiming::get_timestamp_ms(); + return true; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(sendFrameCallback, nullptr, &defaultConfiguration); + + // Send the message + auto data = std::unique_ptr(new CANMessageDataView(dataToSent.data(), dataToSent.size())); + ASSERT_TRUE(manager.protocol_transmit_message(pgnToSent, data, originator, nullptr, nullptr, nullptr)); + ASSERT_TRUE(manager.has_session(originator, nullptr)); + // We shouldn't be able to broadcast another message + data.reset(new CANMessageDataView(dataToSent.data(), dataToSent.size())); // Data get's moved, so we need to create a new one + ASSERT_FALSE(manager.protocol_transmit_message(pgnToSent, data, originator, nullptr, nullptr, nullptr)); + // Also not a message with a different PGN + data.reset(new CANMessageDataView(dataToSent.data(), dataToSent.size())); // Data get's moved, so we need to create a new one + ASSERT_FALSE(manager.protocol_transmit_message(pgnToSent + 1, data, originator, nullptr, nullptr, nullptr)); + + // Wait for the transmission to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 4) && (SystemTiming::get_time_elapsed_ms(time) < 3 * 200)) + { + manager.update(); + } + ASSERT_EQ(frameCount, 4); + + // We expect the transmission to take the minimum time between frames as we update continuously, plus some margin, by default that should be 50ms + EXPECT_NEAR(SystemTiming::get_time_elapsed_ms(time), 3 * 50, 5); + + // After the transmission is finished, the session should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, nullptr)); +} + +// Test case for receiving a destination specific message +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMessageReceiving) +{ + constexpr std::array dataToReceive = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + + std::uint8_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &message) { + CANIdentifier identifier = message.get_identifier(); + EXPECT_EQ(identifier.get_parameter_group_number(), 0xFEEB); + EXPECT_EQ(identifier.get_priority(), CANIdentifier::CANPriority::PriorityDefault6); + EXPECT_EQ(message.get_source_control_function(), originator); // Since we are the receiver, the originator should be the source + EXPECT_EQ(message.get_destination_control_function(), receiver); // Since we are the receiver, the receiver should be the destination + EXPECT_FALSE(message.is_broadcast()); + EXPECT_EQ(message.get_data_length(), dataToReceive.size()); + for (std::size_t i = 0; i < dataToReceive.size(); i++) + { + EXPECT_EQ(message.get_data()[i], dataToReceive[i]); + } + messageCount++; + }; + + std::uint8_t frameCount = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, receiver); // Since it's a response, the receiver should be the destination + EXPECT_EQ(destinationControlFunction, originator); // Since it's a response, the originator should be the destination + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + switch (frameCount) + { + case 0: + // We expect a clear to send (CTS) message as response to the request to send (RTS) message + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 17); // CTS control byte + EXPECT_EQ(data[1], 2); // Number of packets + EXPECT_EQ(data[2], 1); // Next packet to send + EXPECT_EQ(data[3], 0xFF); // Reserved + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + case 1: + // We expect another clear to send (CTS) message as response to the second data frame + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 17); // CTS control byte + EXPECT_EQ(data[1], 2); // Number of packets + EXPECT_EQ(data[2], 3); // Next packet to send + EXPECT_EQ(data[3], 0xFF); // Reserved + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + case 2: + // We expect a end of message acknowledge (EOMA) message as response to the fourth data frame + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 19); // EOMA control byte + EXPECT_EQ(data[1], 23); // Data Length + EXPECT_EQ(data[2], 0); // Data Length MSB + EXPECT_EQ(data[3], 4); // Number of packets + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + default: + EXPECT_TRUE(false); + } + frameCount++; + return true; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(sendFrameCallback, receiveMessageCallback, &defaultConfiguration); + + // Make the manager receive request to send (RTS) message + manager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + receiver, // Since this is a request, the receiver should be the destination + originator, // Since this is a request, the originator should be the source + { + 16, // RTS Mux + 23, // Data Length + 0, // Data Length MSB + 4, // Number of packets + 2, // Limit max number of packets in CTS + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + ASSERT_TRUE(manager.has_session(originator, receiver)); + + // Wait for a CTS message to be sent + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 1) && (SystemTiming::get_time_elapsed_ms(time) < 1250)) // timeout T3=1250ms + { + manager.update(); + } + ASSERT_EQ(frameCount, 1); + + // Make the manager receive the first two data frames + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, // Since this is data transfer, the receiver should be the destination + originator, // Since this is data transfer, the originator should be the source + { + 1, // Sequence number + dataToReceive[0], + dataToReceive[1], + dataToReceive[2], + dataToReceive[3], + dataToReceive[4], + dataToReceive[5], + dataToReceive[6], + })); + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, // Since this is data transfer, the receiver should be the destination + originator, // Since this is data transfer, the originator should be the source + { + 2, // Sequence number + dataToReceive[7], + dataToReceive[8], + dataToReceive[9], + dataToReceive[10], + dataToReceive[11], + dataToReceive[12], + dataToReceive[13], + })); + + // Wait for a CTS message to be sent + time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 2) && (SystemTiming::get_time_elapsed_ms(time) < 1250)) // timeout T3=1250ms + { + manager.update(); + } + + ASSERT_EQ(frameCount, 2); + + // Make the manager receive the third and fourth data frame + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, // Since this is data transfer, the receiver should be the destination + originator, // Since this is data transfer, the originator should be the source + { + 3, // Sequence number + dataToReceive[14], + dataToReceive[15], + dataToReceive[16], + dataToReceive[17], + dataToReceive[18], + dataToReceive[19], + dataToReceive[20], + })); + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, // Since this is data transfer, the receiver should be the destination + originator, // Since this is data transfer, the originator should be the source + { + 4, // Sequence number + dataToReceive[21], + dataToReceive[22], + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + })); + + // Wait for a EOMA message to be sent + time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 3) && (SystemTiming::get_time_elapsed_ms(time) < 1250)) // timeout T3=1250ms + { + manager.update(); + } + ASSERT_EQ(frameCount, 3); + + // We now expect the message to be received + ASSERT_EQ(messageCount, 1); + + // After the transmission is finished, the session should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, receiver)); +} + +void check_abort_message(const CANMessage &message, const std::uint8_t abortReason, const uint32_t parameterGroupNumber) +{ + ASSERT_EQ(message.get_identifier().get_parameter_group_number(), 0xEC00); // Transport Protocol Connection Management + ASSERT_EQ(message.get_data_length(), 8); + ASSERT_EQ(message.get_data()[0], 255); // Abort control byte + ASSERT_EQ(message.get_data()[1], abortReason); + ASSERT_EQ(message.get_data()[2], 0xFF); // Reserved + ASSERT_EQ(message.get_data()[3], 0xFF); // Reserved + ASSERT_EQ(message.get_data()[4], 0xFF); // Reserved + ASSERT_EQ(message.get_data()[5], parameterGroupNumber & 0xFF); + ASSERT_EQ(message.get_data()[6], (parameterGroupNumber >> 8) & 0xFF); + ASSERT_EQ(message.get_data()[7], (parameterGroupNumber >> 16) & 0xFF); +} + +// Test case for timeout when initiating destination specific message +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificTimeoutInitiation) +{ + constexpr std::array dataToTransfer = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + std::deque originatorQueue; + std::deque receiverQueue; + + std::size_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &) { + messageCount++; + }; + + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + CANMessage message(0); //! TODO: hack for now, will be fixed when we remove CANNetwork Singleton + std::uint32_t identifier = test_helpers::create_ext_can_id(static_cast(priority), + parameterGroupNumber, + sourceControlFunction, + destinationControlFunction); + message.set_identifier(CANIdentifier(identifier)); + message.set_source_control_function(sourceControlFunction); + message.set_destination_control_function(destinationControlFunction); + message.set_data(data.begin(), data.size()); + + if (sourceControlFunction == originator) + { + originatorQueue.push_back(message); + } + else if (sourceControlFunction == receiver) + { + receiverQueue.push_back(message); + } + else + { + // Unexpected source, fail the test + EXPECT_TRUE(false); + } + return true; + }; + + // Create the transport protocol managers + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager txManager(sendFrameCallback, nullptr, &defaultConfiguration); + TransportProtocolManager rxManager(sendFrameCallback, receiveMessageCallback, &defaultConfiguration); + + // TX will experience no response to request to send (RTS) message, and is expected to timeout after T3=1250ms + // RX will experience no response to clear to send (CTS) message, and is expected to timeout after T2=1250ms + auto data = std::unique_ptr(new CANMessageDataView(dataToTransfer.data(), dataToTransfer.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(txManager.has_session(originator, receiver)); + + // Make the originator send the request to send (RTS) message and forward it to the receiver + txManager.update(); + ASSERT_EQ(originatorQueue.size(), 1); + std::uint32_t txSessionUpdateTime = SystemTiming::get_timestamp_ms(); + rxManager.process_message(originatorQueue.front()); // Notify receiver of the request to send (RTS) message + originatorQueue.pop_front(); + ASSERT_TRUE(rxManager.has_session(originator, receiver)); + + // The receiver should respond with a clear to send (CTS) message + while (receiverQueue.empty() || SystemTiming::time_expired_ms(txSessionUpdateTime, 200)) // Receiver should respond within Tr=200ms + { + rxManager.update(); + } + std::uint32_t rxSessionUpdateTime = SystemTiming::get_timestamp_ms(); + ASSERT_EQ(receiverQueue.size(), 1); + receiverQueue.pop_front(); // Discard the clear to send (CTS) message + + // Wait for both the originator and receiver to timeout + std::uint32_t txSessionRemovalTime = 0; + std::uint32_t rxSessionRemovalTime = 0; + while (SystemTiming::get_time_elapsed_ms(rxSessionUpdateTime) < 1500 && (txSessionRemovalTime == 0 || rxSessionRemovalTime == 0)) + { + txManager.update(); + if (!txManager.has_session(originator, receiver) && (txSessionRemovalTime == 0)) + { + txSessionRemovalTime = SystemTiming::get_timestamp_ms(); + } + + rxManager.update(); + if (!rxManager.has_session(originator, receiver) && (rxSessionRemovalTime == 0)) + { + rxSessionRemovalTime = SystemTiming::get_timestamp_ms(); + } + } + + // For the originator side, a connection is established only when the first CTS is received, hence we expect no message to be sent + ASSERT_TRUE(originatorQueue.empty()); + + // For the receiver side, a connection is established as soon as the CTS is sent, hence we do expect an abort message to be sent + ASSERT_EQ(receiverQueue.size(), 1); + check_abort_message(receiverQueue.front(), 3, 0xFEEB); // Abort reason 3: Connection timeout + receiverQueue.pop_front(); + + // Check for correct timeouts, and session removal + ASSERT_NEAR(txSessionRemovalTime - txSessionUpdateTime, 1250, 5); // T3=1250ms + ASSERT_NEAR(rxSessionRemovalTime - rxSessionUpdateTime, 1250, 5); // T2=1250ms + ASSERT_FALSE(txManager.has_session(originator, receiver)); + ASSERT_FALSE(rxManager.has_session(originator, receiver)); + ASSERT_EQ(messageCount, 0); // No message should be received +} + +// Test case for timeout of destination specific message completion +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificTimeoutCompletion) +{ + constexpr std::array dataToTransfer = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + std::deque originatorQueue; + std::deque receiverQueue; + + std::size_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &) { + messageCount++; + }; + + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + CANMessage message(0); //! TODO: hack for now, will be fixed when we remove CANNetwork Singleton + std::uint32_t identifier = test_helpers::create_ext_can_id(static_cast(priority), + parameterGroupNumber, + sourceControlFunction, + destinationControlFunction); + message.set_identifier(CANIdentifier(identifier)); + message.set_source_control_function(sourceControlFunction); + message.set_destination_control_function(destinationControlFunction); + message.set_data(data.begin(), data.size()); + + if (sourceControlFunction == originator) + { + originatorQueue.push_back(message); + } + else if (sourceControlFunction == receiver) + { + receiverQueue.push_back(message); + } + else + { + // Unexpected source, fail the test + EXPECT_TRUE(false); + } + return true; + }; + + // Create the transport protocol managers + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager txManager(sendFrameCallback, nullptr, &defaultConfiguration); + TransportProtocolManager rxManager(sendFrameCallback, receiveMessageCallback, &defaultConfiguration); + + // RX will experience a missing last data frame, and is expected to timeout after T1=750ms + // TX will experience a missing end of message acknowledge (EOMA) message, and is expected to timeout after T3=1250ms + auto data = std::unique_ptr(new CANMessageDataView(dataToTransfer.data(), dataToTransfer.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(txManager.has_session(originator, receiver)); + + // Make the originator send the request to send (RTS) message and forward it to the receiver + txManager.update(); + ASSERT_EQ(originatorQueue.size(), 1); + rxManager.process_message(originatorQueue.front()); // Notify receiver of request to send (RTS) message + originatorQueue.pop_front(); + ASSERT_TRUE(rxManager.has_session(originator, receiver)); + + // Wait for the receiver to respond with a clear to send (CTS) message and forward it to the originator + std::uint32_t rxSessionUpdateTime = SystemTiming::get_timestamp_ms(); + while (receiverQueue.empty() || SystemTiming::time_expired_ms(rxSessionUpdateTime, 200)) // Receiver should respond within Tr=200ms + { + rxManager.update(); + } + ASSERT_EQ(receiverQueue.size(), 1); + txManager.process_message(receiverQueue.front()); // Notify originator of clear to send (CTS) message + receiverQueue.pop_front(); + + // Wait for the originator to send all 3 data frames and forward them to the receiver + std::uint32_t txSessionUpdateTime = SystemTiming::get_timestamp_ms(); + while ((originatorQueue.size() != 3) || SystemTiming::time_expired_ms(txSessionUpdateTime, 600)) // Originator should respond with all 3 data frames within 3*(Tr=200ms)=600ms + { + txManager.update(); + } + ASSERT_EQ(originatorQueue.size(), 3); + rxManager.process_message(originatorQueue.front()); // Notify receiver of first data frame + originatorQueue.pop_front(); + std::this_thread::sleep_for(std::chrono::milliseconds(125)); // Arbitrary delay the second data frame + rxManager.process_message(originatorQueue.front()); // Notify receiver of second data frame + rxSessionUpdateTime = SystemTiming::get_timestamp_ms(); + originatorQueue.pop_front(); + originatorQueue.pop_front(); // Discard the third data frame + + // Wait for both the originator and receiver to timeout + std::uint32_t txSessionRemovalTime = 0; + std::uint32_t rxSessionRemovalTime = 0; + while (SystemTiming::get_time_elapsed_ms(rxSessionUpdateTime) < 1500 && (txSessionRemovalTime == 0 || rxSessionRemovalTime == 0)) + { + txManager.update(); + if (!txManager.has_session(originator, receiver) && (txSessionRemovalTime == 0)) + { + txSessionRemovalTime = SystemTiming::get_timestamp_ms(); + } + + rxManager.update(); + if (!rxManager.has_session(originator, receiver) && (rxSessionRemovalTime == 0)) + { + rxSessionRemovalTime = SystemTiming::get_timestamp_ms(); + } + } + + // For both sides, a connection should've been established, hence we expect an abort message to be sent from both the originator and receiver + ASSERT_EQ(originatorQueue.size(), 1); + ASSERT_EQ(receiverQueue.size(), 1); + check_abort_message(receiverQueue.front(), 3, 0xFEEB); // Abort reason 3: Connection timeout + originatorQueue.pop_front(); + receiverQueue.pop_front(); + + // Check for correct timeouts, and session removal + ASSERT_NEAR(txSessionRemovalTime - txSessionUpdateTime, 1250, 5); // T3=1250ms + ASSERT_NEAR(rxSessionRemovalTime - rxSessionUpdateTime, 750, 5); // T1=750ms + ASSERT_FALSE(txManager.has_session(originator, receiver)); + ASSERT_FALSE(rxManager.has_session(originator, receiver)); + ASSERT_EQ(messageCount, 0); // No message should've been received +} + +// Test case for concurrent destination specific messages +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificConcurrentMessaging) +{ + // We setup a total of 10 concurrent connections: + // + // To test data transfer from different sources to the same destination: + // - 2 connections transferring the same pgn and data + // - 3 connections transferring other combinations of pgn and data + // + // To test data transfer from the same source to different destinations: + // - 2 connections transferring the same pgn and data + // - 3 connections transferring other combinations of pgn and data + + constexpr std::uint32_t pgn1ToReceive = 0xFEEC; + constexpr std::uint32_t pgn2ToReceive = 0xFEEB; + constexpr std::array dataToReceive1 = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + constexpr std::array dataToReceive2 = { 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }; + + auto originator1 = test_helpers::create_mock_control_function(0x01); // Send pgn1ToReceive, dataToReceive1 + auto originator2 = test_helpers::create_mock_control_function(0x02); // Send pgn1ToReceive, dataToReceive1 + auto originator3 = test_helpers::create_mock_control_function(0x03); // Send pgn1ToReceive, dataToReceive2 + auto originator4 = test_helpers::create_mock_control_function(0x04); // Send pgn2ToReceive, dataToReceive1 + auto originator5 = test_helpers::create_mock_control_function(0x05); // Send pgn2ToReceive, dataToReceive2 + auto convergingReceiver = test_helpers::create_mock_control_function(0x07); + + auto divergingOriginator = test_helpers::create_mock_control_function(0x06); + auto receiver1 = test_helpers::create_mock_control_function(0x08); // Receive pgn1ToReceive, dataToReceive1 + auto receiver2 = test_helpers::create_mock_control_function(0x09); // Receive pgn1ToReceive, dataToReceive1 + auto receiver3 = test_helpers::create_mock_control_function(0x0A); // Receive pgn1ToReceive, dataToReceive2 + auto receiver4 = test_helpers::create_mock_control_function(0x0B); // Receive pgn2ToReceive, dataToReceive1 + auto receiver5 = test_helpers::create_mock_control_function(0x0C); // Receive pgn2ToReceive, dataToReceive2 + std::deque originatingQueue; + std::deque receivingQueue; + + std::array completedConnections = { false }; + + auto receiveMessageCallback = [&](const CANMessage &message) { + CANIdentifier identifier = message.get_identifier(); + EXPECT_EQ(identifier.get_priority(), CANIdentifier::CANPriority::PriorityDefault6); + EXPECT_FALSE(message.is_broadcast()); + + std::uint32_t pgnToCheck; + const std::uint8_t *dataToCheck; + std::size_t dataLengthToCheck; + + if ((message.get_destination_control_function() == receiver1) || (message.get_source_control_function() == originator1)) + { + pgnToCheck = pgn1ToReceive; + dataToCheck = dataToReceive1.data(); + dataLengthToCheck = dataToReceive1.size(); + if (message.get_destination_control_function() == receiver1) + { + EXPECT_TRUE(message.get_source_control_function() == divergingOriginator); + completedConnections[0] = true; + } + else + { + EXPECT_TRUE(message.get_destination_control_function() == convergingReceiver); + completedConnections[5] = true; + } + } + else if ((message.get_destination_control_function() == receiver2) || (message.get_source_control_function() == originator2)) + { + pgnToCheck = pgn1ToReceive; + dataToCheck = dataToReceive1.data(); + dataLengthToCheck = dataToReceive1.size(); + if (message.get_destination_control_function() == receiver2) + { + EXPECT_TRUE(message.get_source_control_function() == divergingOriginator); + completedConnections[1] = true; + } + else + { + EXPECT_TRUE(message.get_destination_control_function() == convergingReceiver); + completedConnections[6] = true; + } + } + else if ((message.get_destination_control_function() == receiver3) || (message.get_source_control_function() == originator3)) + { + pgnToCheck = pgn1ToReceive; + dataToCheck = dataToReceive2.data(); + dataLengthToCheck = dataToReceive2.size(); + if (message.get_destination_control_function() == receiver3) + { + EXPECT_TRUE(message.get_source_control_function() == divergingOriginator); + completedConnections[2] = true; + } + else + { + EXPECT_TRUE(message.get_destination_control_function() == convergingReceiver); + completedConnections[7] = true; + } + } + else if ((message.get_destination_control_function() == receiver4) || (message.get_source_control_function() == originator4)) + { + pgnToCheck = pgn2ToReceive; + dataToCheck = dataToReceive1.data(); + dataLengthToCheck = dataToReceive1.size(); + if (message.get_destination_control_function() == receiver4) + { + EXPECT_TRUE(message.get_source_control_function() == divergingOriginator); + completedConnections[3] = true; + } + else + { + EXPECT_TRUE(message.get_destination_control_function() == convergingReceiver); + completedConnections[8] = true; + } + } + else if ((message.get_destination_control_function() == receiver5) || (message.get_source_control_function() == originator5)) + { + pgnToCheck = pgn2ToReceive; + dataToCheck = dataToReceive2.data(); + dataLengthToCheck = dataToReceive2.size(); + if (message.get_destination_control_function() == receiver5) + { + EXPECT_TRUE(message.get_source_control_function() == divergingOriginator); + completedConnections[4] = true; + } + else + { + EXPECT_TRUE(message.get_destination_control_function() == convergingReceiver); + completedConnections[9] = true; + } + } + else + { + // Unexpected source or destination, fail the test + EXPECT_TRUE(false); + } + + EXPECT_EQ(identifier.get_parameter_group_number(), pgnToCheck); + EXPECT_EQ(message.get_data_length(), dataLengthToCheck); + for (std::size_t i = 0; i < dataLengthToCheck; i++) + { + EXPECT_EQ(message.get_data()[i], dataToCheck[i]); + } + }; + + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + CANMessage message(0); //! TODO: hack for now, will be fixed when we remove CANNetwork Singleton + std::uint32_t identifier = test_helpers::create_ext_can_id(static_cast(priority), + parameterGroupNumber, + sourceControlFunction, + destinationControlFunction); + message.set_identifier(CANIdentifier(identifier)); + message.set_source_control_function(sourceControlFunction); + message.set_destination_control_function(destinationControlFunction); + message.set_data(data.begin(), data.size()); + + if ((sourceControlFunction == originator1) || + (sourceControlFunction == originator2) || + (sourceControlFunction == originator3) || + (sourceControlFunction == originator4) || + (sourceControlFunction == originator5) || + (sourceControlFunction == divergingOriginator)) + { + originatingQueue.push_back(message); + } + else if ((sourceControlFunction == receiver1) || + (sourceControlFunction == receiver2) || + (sourceControlFunction == receiver3) || + (sourceControlFunction == receiver4) || + (sourceControlFunction == receiver5) || + (sourceControlFunction == convergingReceiver)) + { + receivingQueue.push_back(message); + } + else + { + // Unexpected source, fail the test + EXPECT_TRUE(false); + } + return true; + }; + + // Create the transport protocol managers + CANNetworkConfiguration configuration; + configuration.set_max_number_transport_protocol_sessions(10); // We need to increase the number of sessions to 10 for this test + TransportProtocolManager txManager(sendFrameCallback, nullptr, &configuration); + TransportProtocolManager rxManager(sendFrameCallback, receiveMessageCallback, &configuration); + + // Send the converging messages + auto data = std::unique_ptr(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, originator1, convergingReceiver, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, originator2, convergingReceiver, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive2.data(), dataToReceive2.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, originator3, convergingReceiver, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn2ToReceive, data, originator4, convergingReceiver, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive2.data(), dataToReceive2.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn2ToReceive, data, originator5, convergingReceiver, nullptr, nullptr)); + + // Send the diverging message + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, divergingOriginator, receiver1, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, divergingOriginator, receiver2, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive2.data(), dataToReceive2.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn1ToReceive, data, divergingOriginator, receiver3, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive1.data(), dataToReceive1.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn2ToReceive, data, divergingOriginator, receiver4, nullptr, nullptr)); + data.reset(new CANMessageDataView(dataToReceive2.data(), dataToReceive2.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgn2ToReceive, data, divergingOriginator, receiver5, nullptr, nullptr)); + + ASSERT_TRUE(txManager.has_session(originator1, convergingReceiver)); + ASSERT_TRUE(txManager.has_session(originator2, convergingReceiver)); + ASSERT_TRUE(txManager.has_session(originator3, convergingReceiver)); + ASSERT_TRUE(txManager.has_session(originator4, convergingReceiver)); + ASSERT_TRUE(txManager.has_session(originator5, convergingReceiver)); + ASSERT_TRUE(txManager.has_session(divergingOriginator, receiver1)); + ASSERT_TRUE(txManager.has_session(divergingOriginator, receiver2)); + ASSERT_TRUE(txManager.has_session(divergingOriginator, receiver3)); + ASSERT_TRUE(txManager.has_session(divergingOriginator, receiver4)); + ASSERT_TRUE(txManager.has_session(divergingOriginator, receiver5)); + + // Wait for the transmissions to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((std::any_of(completedConnections.begin(), completedConnections.end(), [](bool completed) { return !completed; })) && // Wait for all connections to be completed + (SystemTiming::get_time_elapsed_ms(time) < 1250 + 200 + 200 + 200 + 200 + 1250)) // Or, maximum time exceeded for 4 packets with 1 CTS according to ISO 11783-3 + { + if (!originatingQueue.empty()) + { + rxManager.process_message(originatingQueue.front()); + originatingQueue.pop_front(); + } + if (!receivingQueue.empty()) + { + txManager.process_message(receivingQueue.front()); + receivingQueue.pop_front(); + } + txManager.update(); + rxManager.update(); + } + + // Check that all connections are completed + ASSERT_TRUE(std::all_of(completedConnections.begin(), completedConnections.end(), [](bool completed) { return completed; })); + + // After the transmission is finished, the sessions should be removed as indication that connection is closed + ASSERT_FALSE(txManager.has_session(originator1, convergingReceiver)); + ASSERT_FALSE(txManager.has_session(originator2, convergingReceiver)); + ASSERT_FALSE(txManager.has_session(originator3, convergingReceiver)); + ASSERT_FALSE(txManager.has_session(originator4, convergingReceiver)); + ASSERT_FALSE(txManager.has_session(originator5, convergingReceiver)); + ASSERT_FALSE(txManager.has_session(divergingOriginator, receiver1)); + ASSERT_FALSE(txManager.has_session(divergingOriginator, receiver2)); + ASSERT_FALSE(txManager.has_session(divergingOriginator, receiver3)); + ASSERT_FALSE(txManager.has_session(divergingOriginator, receiver4)); + ASSERT_FALSE(txManager.has_session(divergingOriginator, receiver5)); + ASSERT_FALSE(rxManager.has_session(originator1, convergingReceiver)); + ASSERT_FALSE(rxManager.has_session(originator2, convergingReceiver)); + ASSERT_FALSE(rxManager.has_session(originator3, convergingReceiver)); + ASSERT_FALSE(rxManager.has_session(originator4, convergingReceiver)); + ASSERT_FALSE(rxManager.has_session(originator5, convergingReceiver)); + ASSERT_FALSE(rxManager.has_session(divergingOriginator, receiver1)); + ASSERT_FALSE(rxManager.has_session(divergingOriginator, receiver2)); + ASSERT_FALSE(rxManager.has_session(divergingOriginator, receiver3)); + ASSERT_FALSE(rxManager.has_session(divergingOriginator, receiver4)); + ASSERT_FALSE(rxManager.has_session(divergingOriginator, receiver5)); +} + +// Test case for concurrent destination specific and broadcast messages from same source +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificAndBroadcastMessageConcurrent) +{ + constexpr std::uint32_t pgnToReceiveBroadcast = 0xFEEC; + constexpr std::uint32_t pgnToReceiveSpecific = 0xFEEB; + constexpr std::array dataToReceiveBroadcast = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; + constexpr std::array dataToReceiveSpecific = { 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + + std::deque originatingQueue; + std::deque receivingQueue; + + bool broadcastCompleted = false; + bool specificCompleted = false; + + auto receiveMessageCallback = [&](const CANMessage &message) { + CANIdentifier identifier = message.get_identifier(); + EXPECT_EQ(identifier.get_priority(), CANIdentifier::CANPriority::PriorityDefault6); + + if (message.is_broadcast()) + { + EXPECT_EQ(identifier.get_parameter_group_number(), pgnToReceiveBroadcast); + EXPECT_EQ(message.get_data_length(), dataToReceiveBroadcast.size()); + for (std::size_t i = 0; i < dataToReceiveBroadcast.size(); i++) + { + EXPECT_EQ(message.get_data()[i], dataToReceiveBroadcast[i]); + } + broadcastCompleted = true; + } + else + { + EXPECT_EQ(identifier.get_parameter_group_number(), pgnToReceiveSpecific); + EXPECT_EQ(message.get_data_length(), dataToReceiveSpecific.size()); + for (std::size_t i = 0; i < dataToReceiveSpecific.size(); i++) + { + EXPECT_EQ(message.get_data()[i], dataToReceiveSpecific[i]); + } + specificCompleted = true; + } + }; + + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + CANMessage message(0); //! TODO: hack for now, will be fixed when we remove CANNetwork Singleton + message.set_source_control_function(sourceControlFunction); + message.set_data(data.begin(), data.size()); + if (destinationControlFunction == nullptr) + { + // Broadcast message + message.set_identifier(CANIdentifier( + test_helpers::create_ext_can_id_broadcast(static_cast(priority), + parameterGroupNumber, + sourceControlFunction))); + if (sourceControlFunction == originator) + { + originatingQueue.push_back(message); + } + else + { + // Unexpected source, fail the test + EXPECT_TRUE(false); + } + } + else + { + // Destination specific message + message.set_identifier(CANIdentifier( + test_helpers::create_ext_can_id(static_cast(priority), + parameterGroupNumber, + sourceControlFunction, + destinationControlFunction))); + message.set_destination_control_function(destinationControlFunction); + if (sourceControlFunction == originator) + { + originatingQueue.push_back(message); + } + else if (sourceControlFunction == receiver) + { + receivingQueue.push_back(message); + } + else + { + // Unexpected source or destination, fail the test + EXPECT_TRUE(false); + } + } + return true; + }; + + // Create the transport protocol managers + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager txManager(sendFrameCallback, nullptr, &defaultConfiguration); + TransportProtocolManager rxManager(sendFrameCallback, receiveMessageCallback, &defaultConfiguration); + + // Send the broadcast message + auto data = std::unique_ptr(new CANMessageDataView(dataToReceiveBroadcast.data(), dataToReceiveBroadcast.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgnToReceiveBroadcast, data, originator, nullptr, nullptr, nullptr)); + ASSERT_TRUE(txManager.has_session(originator, nullptr)); + + // Send the destination specific message + data.reset(new CANMessageDataView(dataToReceiveSpecific.data(), dataToReceiveSpecific.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(pgnToReceiveSpecific, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(txManager.has_session(originator, receiver)); + + // Wait for the transmissions to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((!broadcastCompleted || !specificCompleted) && // Wait for both connections to be completed, or + (SystemTiming::get_time_elapsed_ms(time) < 1250 + 200 + 200 + 1250)) // maximum time exceeded for 2 packets with 1 CTS according to ISO 11783-3 + { + if (!originatingQueue.empty()) + { + rxManager.process_message(originatingQueue.front()); + originatingQueue.pop_front(); + } + if (!receivingQueue.empty()) + { + txManager.process_message(receivingQueue.front()); + receivingQueue.pop_front(); + } + txManager.update(); + rxManager.update(); + } + + // Check that both transmissions are completed + ASSERT_TRUE(broadcastCompleted); + ASSERT_TRUE(specificCompleted); + + // After the transmission is finished, the sessions should be removed as indication that connection is closed + ASSERT_FALSE(txManager.has_session(originator, nullptr)); + ASSERT_FALSE(txManager.has_session(originator, receiver)); + ASSERT_FALSE(rxManager.has_session(originator, nullptr)); + ASSERT_FALSE(rxManager.has_session(originator, receiver)); +} + +// Test case for abortion of sending destination specific message during initialization +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificAbortInitiation) +{ + constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + std::deque responseQueue; + + std::size_t frameCount = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, originator); + EXPECT_EQ(destinationControlFunction, receiver); + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + switch (frameCount) + { + case 0: + // First we expect a Request to Send (RTS) message + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 16); // RTS control byte + EXPECT_EQ(data[1], 9); // Message size + EXPECT_EQ(data[2], 0); // Message size MSB + EXPECT_EQ(data[3], 2); // Number of packets + EXPECT_EQ(data[4], 16); // Limit number of packets in CTS (should be 16 by default to follow recommendation in ISO 11783-3) + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + + // We respond with an abort message, to deny the connection + responseQueue.push_back(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + sourceControlFunction, + destinationControlFunction, + { + 255, // Abort control byte + 1, // Abort reason 1: Cannot support another connection + 0xFF, // Reserved + 0xFF, // Reserved + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + break; + + default: + EXPECT_TRUE(false); + } + + frameCount++; + return true; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(sendFrameCallback, nullptr, &defaultConfiguration); + + // Send the message + auto data = std::unique_ptr(new CANMessageDataView(dataToSent.data(), dataToSent.size())); + ASSERT_TRUE(manager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(manager.has_session(originator, receiver)); + + // Wait for the transmission to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((!responseQueue.empty()) || ((frameCount < 1) && (SystemTiming::get_time_elapsed_ms(time) < 1250))) + { + if (!responseQueue.empty()) + { + manager.process_message(responseQueue.front()); + responseQueue.pop_front(); + } + manager.update(); + } + + ASSERT_EQ(frameCount, 1); + ASSERT_FALSE(manager.has_session(originator, receiver)); +} + +// Test case for aborting when multiple CTS received by originator after a connection is already established +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMultipleCTS) +{ + constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + std::deque responseQueue; + + std::size_t frameCount = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, originator); + EXPECT_EQ(destinationControlFunction, receiver); + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + switch (frameCount) + { + case 0: + { + // First we expect a Request to Send (RTS) message + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 16); // RTS control byte + EXPECT_EQ(data[1], 9); // Message size + EXPECT_EQ(data[2], 0); // Message size MSB + EXPECT_EQ(data[3], 2); // Number of packets + EXPECT_EQ(data[4], 16); // Limit number of packets in CTS (should be 16 by default to follow recommendation in ISO 11783-3) + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + + // We respond with two clear to send (CTS) message + auto response = test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + sourceControlFunction, + destinationControlFunction, + { + 17, // CTS Mux + 2, // Number of packets + 1, // Next packet to send + 0xFF, // Reserved + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + }); + responseQueue.push_back(response); + responseQueue.push_back(response); + } + break; + + case 1: + // Then we expect an abort message + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 255); // Abort control byte + EXPECT_EQ(data[1], 4); // Abort reason 4: Unexpected CTS + EXPECT_EQ(data[2], 0xFF); // Reserved + EXPECT_EQ(data[3], 0xFF); // Reserved + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + default: + EXPECT_TRUE(false); + } + + frameCount++; + return true; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(sendFrameCallback, nullptr, &defaultConfiguration); + + // Send the message + auto data = std::unique_ptr(new CANMessageDataView(dataToSent.data(), dataToSent.size())); + ASSERT_TRUE(manager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(manager.has_session(originator, receiver)); + + // Wait for the transmission to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((!responseQueue.empty()) || ((frameCount < 2) && (SystemTiming::get_time_elapsed_ms(time) < 1250))) + { + while (!responseQueue.empty()) + { + manager.process_message(responseQueue.front()); + responseQueue.pop_front(); + } + manager.update(); + } + + ASSERT_EQ(frameCount, 2); + ASSERT_FALSE(manager.has_session(originator, receiver)); +} + +// Test case for ignoring random CTS messages +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificRandomCTS) +{ + constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; + + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x02); + auto randomControlFunction = test_helpers::create_mock_control_function(0x03); + std::deque originatorQueue; + std::deque receiverQueue; + + std::size_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &) { + messageCount++; + }; + + std::size_t rxFrameCount = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + CANMessage message(0); //! TODO: hack for now, will be fixed when we remove CANNetwork Singleton + std::uint32_t identifier = test_helpers::create_ext_can_id(static_cast(priority), + parameterGroupNumber, + sourceControlFunction, + destinationControlFunction); + message.set_identifier(CANIdentifier(identifier)); + message.set_source_control_function(sourceControlFunction); + message.set_destination_control_function(destinationControlFunction); + message.set_data(data.begin(), data.size()); + + if (sourceControlFunction == originator) + { + originatorQueue.push_back(message); + } + else if (sourceControlFunction == receiver) + { + receiverQueue.push_back(message); + rxFrameCount++; + } + else + { + // Unexpected source, fail the test + EXPECT_TRUE(false); + } + return true; + }; + + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager txManager(sendFrameCallback, nullptr, &defaultConfiguration); + TransportProtocolManager rxManager(sendFrameCallback, receiveMessageCallback, &defaultConfiguration); + + // Send random CTS message + rxManager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + randomControlFunction, + receiver, + { + 17, // CTS Mux + 2, // Number of packets + 1, // Next packet to send + 0xFF, // Reserved + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + // Send the message + auto data = std::unique_ptr(new CANMessageDataView(dataToSent.data(), dataToSent.size())); + ASSERT_TRUE(txManager.protocol_transmit_message(0xFEEB, data, originator, receiver, nullptr, nullptr)); + ASSERT_TRUE(txManager.has_session(originator, receiver)); + + // Wait for the transmission to finish (or timeout), while sending some more random CTS messages + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((messageCount < 1) && (SystemTiming::get_time_elapsed_ms(time) < 1250 + 200 + 200 + 200 + 200 + 1250)) + { + if (!originatorQueue.empty()) + { + rxManager.process_message(originatorQueue.front()); + originatorQueue.pop_front(); + } + if (!receiverQueue.empty()) + { + txManager.process_message(receiverQueue.front()); + receiverQueue.pop_front(); + } + txManager.update(); + rxManager.update(); + + // Send random CTS message + rxManager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + randomControlFunction, + receiver, + { + 17, // CTS Mux + 4, // Number of packets + 2, // Next packet to send + 0xFF, // Reserved + 0xFF, // Reserved + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + } + + ASSERT_EQ(messageCount, 1); + ASSERT_EQ(rxFrameCount, 2); // One for the CTS, and one for the end of message acknowledgement. + ASSERT_FALSE(txManager.has_session(originator, receiver)); + ASSERT_FALSE(rxManager.has_session(originator, receiver)); +} + +// Test case for rejecting a RTS when exceeding the maximum number of sessions +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificRejectForOutOfResources) +{ + auto originator1 = test_helpers::create_mock_control_function(0x01); + auto originator2 = test_helpers::create_mock_control_function(0x02); + auto receiver = test_helpers::create_mock_control_function(0x0B); + + bool originator1CTSReceived = false; + bool originator2AbortReceived = false; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, receiver); + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + if (destinationControlFunction == originator1) + { + // We expect a CTS message for originator1 + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 17); // CTS control byte + EXPECT_EQ(data[1], 2); // Number of packets + EXPECT_EQ(data[2], 1); // Next packet to send + EXPECT_EQ(data[3], 0xFF); + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEC); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + originator1CTSReceived = true; + } + else if (destinationControlFunction == originator2) + { + // We expect an abort message for originator2 + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 255); // Abort control byte + EXPECT_EQ(data[1], 1); // Abort reason 1: Cannot support another connection + EXPECT_EQ(data[2], 0xFF); // Reserved + EXPECT_EQ(data[3], 0xFF); // Reserved + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEB); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + originator2AbortReceived = true; + } + else + { + // Unexpected source, fail the test + EXPECT_TRUE(false); + } + return true; + }; + + // Create the transport protocol manager + CANNetworkConfiguration configuration; + configuration.set_max_number_transport_protocol_sessions(1); // We limit the number of sessions to 1 for this test + TransportProtocolManager manager(sendFrameCallback, nullptr, &configuration); + + // Send first RTS from originator1 + manager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + receiver, + originator1, + { + 16, // RTS control byte + 9, // Message size + 0, // Message size MSB + 2, // Number of packets + 0xFF, + 0xEC, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + // Send second RTS from originator2 + manager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + receiver, + originator2, + { + 16, // RTS control byte + 9, // Message size + 0, // Message size MSB + 2, // Number of packets + 0xFF, + 0xEB, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + // Wait for the transmission to finish (or timeout) + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((!originator1CTSReceived || !originator2AbortReceived) && // Wait for both frames to be sent, or + (SystemTiming::get_time_elapsed_ms(time) < 1250 + 200 + 200 + 1250)) // maximum time exceeded for 2 packets with 1 CTS according to ISO 11783-3 + { + manager.update(); + } + + ASSERT_TRUE(originator1CTSReceived); + ASSERT_TRUE(originator2AbortReceived); + ASSERT_TRUE(manager.has_session(originator1, receiver)); // The first connection should still be active + ASSERT_FALSE(manager.has_session(originator2, receiver)); // The second connection should be rejected +} + +// A test case for overwriting a session when a new RTS is received +TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificOverwriteSession) +{ + auto originator = test_helpers::create_mock_control_function(0x01); + auto receiver = test_helpers::create_mock_control_function(0x0B); + + std::size_t messageCount = 0; + auto receiveMessageCallback = [&](const CANMessage &message) { + ASSERT_EQ(message.get_data_length(), 15); + ASSERT_EQ(message.get_source_control_function(), originator); + ASSERT_EQ(message.get_destination_control_function(), receiver); + ASSERT_EQ(message.get_identifier().get_parameter_group_number(), 0xFEEC); + ASSERT_EQ(message.get_data()[0], 0x01); + ASSERT_EQ(message.get_data()[1], 0x02); + ASSERT_EQ(message.get_data()[2], 0x03); + ASSERT_EQ(message.get_data()[3], 0x04); + ASSERT_EQ(message.get_data()[4], 0x05); + ASSERT_EQ(message.get_data()[5], 0x06); + ASSERT_EQ(message.get_data()[6], 0x07); + ASSERT_EQ(message.get_data()[7], 0x08); + ASSERT_EQ(message.get_data()[8], 0x09); + ASSERT_EQ(message.get_data()[9], 0x0A); + ASSERT_EQ(message.get_data()[10], 0x0B); + ASSERT_EQ(message.get_data()[11], 0x0C); + ASSERT_EQ(message.get_data()[12], 0x0D); + ASSERT_EQ(message.get_data()[13], 0x0E); + ASSERT_EQ(message.get_data()[14], 0x0F); + messageCount++; + }; + + std::size_t frameCount = 0; + auto sendFrameCallback = [&](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { + EXPECT_EQ(data.size(), 8); + EXPECT_EQ(sourceControlFunction, receiver); + EXPECT_EQ(destinationControlFunction, originator); + EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); + + switch (frameCount) + { + case 0: + // First we expect a CTS message for the first RTS + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 17); // CTS control byte + EXPECT_EQ(data[1], 2); // Number of packets + EXPECT_EQ(data[2], 1); // Next packet to send + EXPECT_EQ(data[3], 0xFF); + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEC); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + case 1: + // Then we expect a CTS message for the second RTS + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 17); // CTS control byte + EXPECT_EQ(data[1], 3); // Number of packets + EXPECT_EQ(data[2], 1); // Next packet to send + EXPECT_EQ(data[3], 0xFF); + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEC); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + case 2: + // Then we expect a End of Message Acknowledgement for the overwritten session + EXPECT_EQ(parameterGroupNumber, 0xEC00); + EXPECT_EQ(data[0], 19); // End of Message Acknowledgement control byte + EXPECT_EQ(data[1], 15); // Number of bytes + EXPECT_EQ(data[2], 0); // Number of bytes MSB + EXPECT_EQ(data[3], 3); // Total number of packets + EXPECT_EQ(data[4], 0xFF); // Reserved + EXPECT_EQ(data[5], 0xEC); // PGN LSB + EXPECT_EQ(data[6], 0xFE); // PGN middle byte + EXPECT_EQ(data[7], 0x00); // PGN MSB + break; + + default: + EXPECT_TRUE(false); + } + + frameCount++; + return true; + }; + + // Create the transport protocol manager + CANNetworkConfiguration defaultConfiguration; + TransportProtocolManager manager(sendFrameCallback, receiveMessageCallback, &defaultConfiguration); + + // Send first RTS + manager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + receiver, + originator, + { + 16, // RTS control byte + 9, // Message size + 0, // Message size MSB + 2, // Number of packets + 0xFF, + 0xEC, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + // Wait for the first CTS to be sent + std::uint32_t time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 1) && (SystemTiming::get_time_elapsed_ms(time) < 1250)) + { + manager.update(); + } + + ASSERT_EQ(frameCount, 1); + + // Send the first data frame + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, + originator, + { + 1, // Sequence number + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + })); + + // Now we overwrite with a new RTS + manager.process_message(test_helpers::create_message( + 7, + 0xEC00, // Transport Protocol Connection Management + receiver, + originator, + { + 16, // RTS control byte + 15, // Message size + 0, // Message size MSB + 3, // Number of packets + 0xFF, + 0xEC, // PGN LSB + 0xFE, // PGN middle byte + 0x00, // PGN MSB + })); + + // Wait for the second CTS to be sent + time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 2) && (SystemTiming::get_time_elapsed_ms(time) < 1250)) + { + manager.update(); + } + + ASSERT_EQ(frameCount, 2); + + // Send the 3 data frames + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, + originator, + { + 1, // Sequence number + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + })); + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, + originator, + { + 2, // Sequence number + 0x08, + 0x09, + 0x0A, + 0x0B, + 0x0C, + 0x0D, + 0x0E, + })); + manager.process_message(test_helpers::create_message( + 7, + 0xEB00, // Transport Protocol Data Transfer + receiver, + originator, + { + 3, // Sequence number + 0x0F, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + })); + + // Wait for the End of Message Acknowledgement to be sent + time = SystemTiming::get_timestamp_ms(); + while ((frameCount < 3) && (SystemTiming::get_time_elapsed_ms(time) < 1250)) + { + manager.update(); + } + + ASSERT_EQ(frameCount, 3); + ASSERT_EQ(messageCount, 1); + + // After the transmission is finished, the sessions should be removed as indication that connection is closed + ASSERT_FALSE(manager.has_session(originator, receiver)); +} \ No newline at end of file diff --git a/utility/include/isobus/utility/data_span.hpp b/utility/include/isobus/utility/data_span.hpp new file mode 100644 index 00000000..678215db --- /dev/null +++ b/utility/include/isobus/utility/data_span.hpp @@ -0,0 +1,79 @@ +//================================================================================================ +/// @file can_message_data.hpp +/// +/// @brief Contains common types and functions for working with an arbitrary amount of items. +/// @author Daan Steenbergen +/// +/// @copyright 2023 Open Agriculture +//================================================================================================ +#ifndef DATA_SPAN_HPP +#define DATA_SPAN_HPP + +#include +#include +#include + +namespace isobus +{ + //================================================================================================ + /// @class DataSpan + /// + /// @brief A class that represents a span of data of arbitrary length. + //================================================================================================ + template + class DataSpan + { + public: + /// @brief Construct a new DataSpan object of a writeable array. + /// @param ptr pointer to the buffer to use. + /// @param len The number of elements in the buffer. + DataSpan(T *ptr, std::size_t size) : + ptr(ptr), + _size(size) + { + } + + /// @brief Get the element at the given index. + /// @param index The index of the element to get. + /// @return The element at the given index. + T &operator[](std::size_t index) + { + return ptr[index * sizeof(T)]; + } + + /// @brief Get the element at the given index. + /// @param index The index of the element to get. + /// @return The element at the given index. + T const &operator[](std::size_t index) const + { + return ptr[index * sizeof(T)]; + } + + /// @brief Get the size of the data span. + /// @return The size of the data span. + std::size_t size() const + { + return _size; + } + + /// @brief Get the begin iterator. + /// @return The begin iterator. + T *begin() const + { + return ptr; + } + + /// @brief Get the end iterator. + /// @return The end iterator. + T *end() const + { + return ptr + _size * sizeof(T); + } + + private: + T *ptr; + std::size_t _size; + bool _isConst; + }; +} +#endif // DATA_SPAN_HPP From dbc407773408af410756d393a6ed4e6b618d0073 Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Wed, 20 Dec 2023 09:48:34 +0100 Subject: [PATCH 2/9] feat(etp): complete rewrite of extended transport protocol --- .../include/isobus/isobus/can_callbacks.hpp | 9 + .../can_extended_transport_protocol.hpp | 448 +++-- .../isobus/isobus/can_network_manager.hpp | 2 +- .../isobus/isobus/can_transport_protocol.hpp | 83 +- .../src/can_extended_transport_protocol.cpp | 1518 +++++++++-------- isobus/src/can_network_manager.cpp | 20 +- isobus/src/can_transport_protocol.cpp | 125 +- 7 files changed, 1287 insertions(+), 918 deletions(-) diff --git a/isobus/include/isobus/isobus/can_callbacks.hpp b/isobus/include/isobus/isobus/can_callbacks.hpp index 852a2c77..710b54a3 100644 --- a/isobus/include/isobus/isobus/can_callbacks.hpp +++ b/isobus/include/isobus/isobus/can_callbacks.hpp @@ -10,6 +10,7 @@ #ifndef CAN_CALLBACKS_HPP #define CAN_CALLBACKS_HPP +#include #include "isobus/isobus/can_message.hpp" namespace isobus @@ -36,6 +37,14 @@ namespace isobus /// @brief A callback for control functions to get CAN messages using CANLibCallback = void (*)(const CANMessage &message, void *parentPointer); + /// @brief A callback for communicating CAN messages + using CANMessageCallback = std::function; + /// @brief A callback for communicating CAN message frames + using CANMessageFrameCallback = std::function sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority)>; ///< A callback for sending a CAN frame /// @brief A callback that can inform you when a control function changes state between online and offline using ControlFunctionStateCallback = void (*)(std::shared_ptr controlFunction, ControlFunctionState state); /// @brief A callback to get chunks of data for transfer by a protocol diff --git a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp index d5d4615f..a2d18ede 100644 --- a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp @@ -2,8 +2,9 @@ /// @file can_extended_transport_protocol.hpp /// /// @brief A protocol class that handles the ISO11783 extended transport protocol. -/// Designed for packets larger than 1785 bytes. +/// Designed for destination specific packets larger than 1785 bytes. /// @author Adrian Del Grosso +/// @author Daan Steenbergen /// /// @copyright 2022 Adrian Del Grosso //================================================================================================ @@ -11,9 +12,11 @@ #ifndef CAN_EXTENDED_TRANSPORT_PROTOCOL_HPP #define CAN_EXTENDED_TRANSPORT_PROTOCOL_HPP -#include "isobus/isobus/can_badge.hpp" #include "isobus/isobus/can_control_function.hpp" -#include "isobus/isobus/can_protocol.hpp" +#include "isobus/isobus/can_message.hpp" +#include "isobus/isobus/can_message_data.hpp" +#include "isobus/isobus/can_message_frame.hpp" +#include "isobus/isobus/can_network_configuration.hpp" namespace isobus { @@ -22,45 +25,45 @@ namespace isobus /// /// @brief A class that handles the ISO11783 extended transport protocol. /// @details This class handles transmission and reception of CAN messages more than 1785 bytes. - /// Simply call send_can_message on the network manager with an appropriate data length, - /// and the protocol will be automatically selected to be used. + /// Simply call Simply call `CANNetworkManager::send_can_message()` + /// with an appropriate data length, and the protocol will be automatically selected to be used. //================================================================================================ - class ExtendedTransportProtocolManager : public CANLibProtocol + class ExtendedTransportProtocolManager { public: - /// @brief A list of all defined abort reasons in ISO11783 - enum class ConnectionAbortReason - { - Reserved = 0, ///< Reserved, not to be used, but should be tolerated - AlreadyInConnectionManagedSessionAndCannotSupportAnother = 1, ///< We are already in a session and can't support another - SystemResourcesNeededForAnotherTask = 2, ///< Session must be aborted because the system needs resources - Timeout = 3, ///< General timeout - ClearToSendReceivedWhenDataTransferInProgress = 4, ///< A CTS was received while already processing the last CTS - MaximumRetransmitRequestLimitReached = 5, ///< Maxmimum retries for the data has been reached - UnexpectedDataTransferPacket = 6, ///< A data packet was received outside the proper state - BadSequenceNumber = 7, ///< Incorrect sequence number was received and cannot be recovered - DuplicateSequenceNumber = 8, ///< Re-received a sequence number we've already processed - UnexpectedEDPOPacket = 9, ///< EDPO Received in an invalid state - UnexpectedEDPOPgn = 10, ///< Unexpected PGN in received EDPO - EDPONumberOfPacketsGreaterThanClearToSend = 11, ///< Number of EDPO packets is > CTS - BadEDPOOffset = 12, ///< EDPO offset was invalid - DeprecatedReason = 13, ///< Don't use this. Use AnyOtherReason instead - UnexpectedECTSPgn = 14, ///< PGN in received ECTS doesn't match session - ECTSRequestedPacketsExceedsMessageSize = 15, ///< ETP Can't support a message this large (CANMessage::ABSOLUTE_MAX_MESSAGE_LENGTH) - AnyOtherReason = 254 ///< Any other error not enumerated above, 0xFE - }; - /// @brief The states that a ETP session could be in. Used for the internal state machine. enum class StateMachineState { None, ///< Protocol session is not in progress - RequestToSend, ///< We are sending the request to send message + SendRequestToSend, ///< We are sending the request to send message WaitForClearToSend, ///< We are waiting for a clear to send message - RxDataSession, ///< We are receiving data packets - ClearToSend, ///< We are sending clear to send message - WaitForExtendedDataPacketOffset, ///< We are waiting for an EDPO message - TxDataSession, ///< We are transmitting EDPOs and data packets - WaitForEndOfMessageAcknowledge ///< We are waiting for an end of message acknowledgement + SendClearToSend, ///< We are sending clear to send message + WaitForDataPacketOffset, ///< We are waiting for a data packet offset message + SendDataPacketOffset, ///< We are sending a data packet offset message + WaitForDataTransferPacket, ///< We are waiting for data transfer packets + SendDataTransferPackets, ///< A Tx data session is in progress + WaitForEndOfMessageAcknowledge, ///< We are waiting for an end of message acknowledgement + }; + + /// @brief A list of all defined abort reasons in ISO11783 + enum class ConnectionAbortReason : std::uint8_t + { + Reserved = 0, ///< Reserved, not to be used, but should be tolerated + AlreadyInCMSession = 1, ///< We are already in a connection mode session and can't support another + SystemResourcesNeeded = 2, ///< Session must be aborted because the system needs resources + Timeout = 3, ///< General timeout + ClearToSendReceivedWhileTransferInProgress = 4, ///< A CTS was received while already processing the last CTS + MaximumRetransmitRequestLimitReached = 5, ///< Maximum retries for the data has been reached + UnexpectedDataTransferPacketReceived = 6, ///< A data packet was received outside the proper state + BadSequenceNumber = 7, ///< Incorrect sequence number was received and cannot be recovered + DuplicateSequenceNumber = 8, ///< Re-received a sequence number we've already processed + UnexpectedDataPacketOffsetReceived = 9, // Received a data packet offset outside the proper state + UnexpectedDataPacketOffsetPGN = 10, ///< Received a data packet offset with an unexpected PGN + DataPacketOffsetExceedsClearToSend = 11, ///< Received a number of packets in EDPO greater than CTS + BadDataPacketOffset = 12, ///< Received a data packet offset that is incorrect + UnexpectedClearToSendPGN = 14, ///< Received a CTS with an unexpected PGN + NumberOfClearToSendPacketsExceedsMessage = 15, ///< Received a CTS with a number of packets greater than the message + AnyOtherError = 250 ///< Any reason not defined in the standard }; //================================================================================================ @@ -79,162 +82,333 @@ namespace isobus }; /// @brief A useful way to compare session objects to each other for equality - /// @param[in] obj The object to compare against + /// @param[in] obj The object to compare to /// @returns true if the objects are equal, false if not - bool operator==(const ExtendedTransportProtocolSession &obj); + bool operator==(const ExtendedTransportProtocolSession &obj) const; + + /// @brief Checks if the source and destination control functions match the given control functions. + /// @param[in] other_source The control function to compare with the source control function. + /// @param[in] other_destination The control function to compare with the destination control function. + /// @returns True if the source and destination control functions match the given control functions, false otherwise. + bool matches(std::shared_ptr other_source, std::shared_ptr other_destination) const; + + /// @brief Get the direction of the session + /// @return The direction of the session + Direction get_direction() const; + + /// @brief Get the state of the session + /// @return The state of the session + StateMachineState get_state() const; /// @brief Get the total number of bytes that will be sent or received in this session /// @return The length of the message in number of bytes - std::uint32_t get_message_data_length() const; + std::uint32_t get_message_length() const; - private: + /// @brief Get the data buffer for the session + /// @return The data buffer for the session + CANMessageData &get_data() const; + + /// @brief Get the control function that is sending the message + /// @return The source control function + std::shared_ptr get_source() const; + + /// @brief Get the control function that is receiving the message + /// @return The destination control function + std::shared_ptr get_destination() const; + + /// @brief Get the parameter group number of the message + /// @return The PGN of the message + std::uint32_t get_parameter_group_number() const; + + protected: friend class ExtendedTransportProtocolManager; ///< Allows the ETP manager full access - /// @brief The constructor for an ETP session - /// @param[in] sessionDirection Tx or Rx - /// @param[in] canPortIndex The CAN channel index for the session - ExtendedTransportProtocolSession(Direction sessionDirection, std::uint8_t canPortIndex); + /// @brief Factory method to create a new receive session + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the DPO message + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @returns A new receive session + static ExtendedTransportProtocolSession create_receive_session(std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize, + std::uint32_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination); + + /// @brief Factory method to create a new transmit session + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the DPO message + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + /// @returns A new transmit session + static ExtendedTransportProtocolSession create_transmit_session(std::uint32_t parameterGroupNumber, + std::unique_ptr data, + std::shared_ptr source, + std::shared_ptr destination, + std::uint8_t clearToSendPacketMax, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); + + /// @brief Set the state of the session + /// @param[in] value The state to set the session to + void set_state(StateMachineState value); + + /// @brief Get the number of packets to be sent with the current DPO + /// @return The number of packets to be sent with the current DPO + std::uint8_t get_dpo_number_of_packets_remaining() const; + + /// @brief Set the number of packets to be sent with the current DPO + /// @param[in] value The number of packets to be sent with the current DPO + void set_dpo_number_of_packets(std::uint8_t value); + + /// @brief Get the number of packets that will be sent with the current DPO + /// @return The number of packets that will be sent with the current DPO + std::uint8_t get_dpo_number_of_packets() const; + + /// @brief Get the maximum number of packets that can be sent per DPO as indicated by the CTS message + /// @return The maximum number of packets that can be sent per DPO as indicated by the CTS message + std::uint8_t get_cts_number_of_packet_limit() const; + + /// @brief Set the maximum number of packets that can be sent per DPO as indicated by the CTS message + /// @param value The maximum number of packets that can be sent per DPO as indicated by the CTS message + void set_cts_number_of_packet_limit(std::uint8_t value); + + /// @brief Get the last sequence number that was processed + /// @return The last sequence number that was processed + std::uint8_t get_last_sequence_number() const; + + /// @brief Get the last packet number that was processed + /// @return The last packet number that was processed + std::uint32_t get_last_packet_number() const; + + /// @brief Set the last sequence number that will be processed + /// @param[in] value The last sequence number that will be processed + void set_last_sequency_number(std::uint8_t value); + + /// @brief Set the last acknowledged packet number by the receiver + /// @param[in] value The last acknowledged packet number by the receiver + void set_acknowledged_packet_number(std::uint32_t value); + + /// @brief Get the last acknowledged packet number by the receiver + /// @return The last acknowledged packet number by the receiver + std::uint32_t get_last_acknowledged_packet_number() const; + + /// @brief Get the number of packets that remain to be sent or received in this session + /// @return The number of packets that remain to be sent or received in this session + std::uint32_t get_number_of_remaining_packets() const; + + /// @brief Get the total number of packets that will be sent or received in this session + /// @return The total number of packets that will be sent or received in this session + std::uint32_t get_total_number_of_packets() const; - /// @brief The destructor for a ETP session - ~ExtendedTransportProtocolSession(); + private: + /// @brief The constructor for a session + /// @param[in] direction The direction of the session + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + ExtendedTransportProtocolSession(Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize, + std::uint32_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); StateMachineState state = StateMachineState::None; ///< The state machine state for this session - CANMessage sessionMessage; ///< A CAN message is used in the session to represent and store data like PGN + + Direction direction; ///< The direction of the session + std::uint32_t parameterGroupNumber; ///< The PGN of the message + std::unique_ptr data; ///< The data buffer for the message + std::uint32_t totalMessageSize; ///< The total size of the message in bytes + std::shared_ptr source; ///< The source control function + std::shared_ptr destination; ///< The destination control function + + std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts + std::uint8_t lastSequenceNumber = 0; ///< The last processed sequence number for this set of packets + std::uint32_t sequenceNumberOffset = 0; ///< The offset of the sequence number relative to the packet number + std::uint32_t lastAcknowledgedPacketNumber = 0; ///< The last acknowledged packet number by the receiver + + std::uint32_t totalNumberOfPackets; ///< The total number of packets that will be sent or received in this session + std::uint8_t clearToSendPacketCount = 0; ///< The number of packets to be sent in response to one CTS + std::uint8_t clearToSendPacketCountMax = 0xFF; ///< The max packets that can be sent per CTS as indicated by the RTS message + TransmitCompleteCallback sessionCompleteCallback = nullptr; ///< A callback that is to be called when the session is completed - DataChunkCallback frameChunkCallback = nullptr; ///< A callback that might be used to get chunks of data to send - std::uint32_t frameChunkCallbackMessageLength = 0; ///< The length of the message that is being sent in chunks void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr - std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts - std::uint32_t lastPacketNumber = 0; ///< The last processed sequence number for this set of packets - std::uint32_t packetCount = 0; ///< The total number of packets to receive or send in this session - std::uint32_t processedPacketsThisSession = 0; ///< The total processed packet count for the whole session so far - const Direction sessionDirection; ///< Represents Tx or Rx session }; - /// @brief The constructor for the TransportProtocolManager - ExtendedTransportProtocolManager(); + static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 20; ///< ETP.CM_RTS Multiplexor + static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 21; ///< ETP.CM_CTS Multiplexor + static constexpr std::uint32_t DATA_PACKET_OFFSET_MULTIPLXOR = 22; ///< ETP.CM_DPO Multiplexor + static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 23; ///< TP.CM_EOMA Multiplexor + static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 255; ///< Abort multiplexor + static constexpr std::uint32_t MAX_PROTOCOL_DATA_LENGTH = 117440505; ///< The max number of bytes that this protocol can transfer + static constexpr std::uint16_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard + static constexpr std::uint16_t T2_T3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard + static constexpr std::uint16_t T4_TIMEOUT_MS = 1050; ///< The t4 timeout as defined by the standard + static constexpr std::uint8_t TR_TIMEOUT_MS = 200; ///< The Tr Timeout as defined by the standard + static constexpr std::uint8_t SEQUENCE_NUMBER_DATA_INDEX = 0; ///< The index of the sequence number in a frame + static constexpr std::uint8_t PROTOCOL_BYTES_PER_FRAME = 7; ///< The number of payload bytes per frame minus overhead of sequence number - /// @brief The destructor for the TransportProtocolManager - ~ExtendedTransportProtocolManager() final; + /// @brief The constructor for the ExtendedTransportProtocolManager, for advanced use only. + /// In most cases, you should use the CANNetworkManager::send_can_message() function to transmit messages. + /// @param[in] sendCANFrameCallback A callback for sending a CAN frame to hardware + /// @param[in] canMessageReceivedCallback A callback for when a complete CAN message is received using the ETP protocol + /// @param[in] configuration The configuration to use for this protocol + ExtendedTransportProtocolManager(const CANMessageFrameCallback &sendCANFrameCallback, + const CANMessageCallback &canMessageReceivedCallback, + const CANNetworkConfiguration *configuration); - /// @brief The protocol's initializer function - void initialize(CANLibBadge) override; + /// @brief Updates all sessions managed by this protocol manager instance. + void update(); - /// @brief A generic way for a protocol to process a received message - /// @param[in] message A received CAN message - void process_message(const CANMessage &message) override; + /// @brief Checks if the source and destination control function have an active session/connection. + /// @param[in] source The source control function for the session + /// @param[in] destination The destination control function for the session + /// @returns true if a matching session was found, false if not + bool has_session(std::shared_ptr source, std::shared_ptr destination); /// @brief A generic way for a protocol to process a received message /// @param[in] message A received CAN message - /// @param[in] parent Provides the context to the actual TP manager object - static void process_message(const CANMessage &message, void *parent); + void process_message(const CANMessage &message); /// @brief The network manager calls this to see if the protocol can accept a long CAN message for processing /// @param[in] parameterGroupNumber The PGN of the message /// @param[in] data The data to be sent - /// @param[in] messageLength The length of the data to be sent /// @param[in] source The source control function /// @param[in] destination The destination control function - /// @param[in] transmitCompleteCallback A callback for when the protocol completes its work + /// @param[in] sessionCompleteCallback A callback for when the protocol completes its work /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - /// @param[in] frameChunkCallback A callback to get some data to send /// @returns true if the message was accepted by the protocol for processing bool protocol_transmit_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *data, - std::uint32_t messageLength, + std::unique_ptr &data, std::shared_ptr source, std::shared_ptr destination, - TransmitCompleteCallback transmitCompleteCallback, - void *parentPointer, - DataChunkCallback frameChunkCallback) override; - - /// @brief Updates the protocol cyclically - void update(CANLibBadge) override; + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); private: - static constexpr std::uint32_t MAX_PROTOCOL_DATA_LENGTH = CANMessage::ABSOLUTE_MAX_MESSAGE_LENGTH; ///< The max payload this protocol can support - static constexpr std::uint32_t MIN_PROTOCOL_DATA_LENGTH = 1786; ///< The min payload this protocol can support - static constexpr std::uint32_t TR_TIMEOUT_MS = 200; ///< The Tr timeout as defined by the standard - static constexpr std::uint32_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard - static constexpr std::uint32_t T2_3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard - static constexpr std::uint32_t TH_TIMEOUT_MS = 500; ///< The Th timeout as defined by the standard - static constexpr std::uint8_t EXTENDED_REQUEST_TO_SEND_MULTIPLEXOR = 0x14; ///< The multiplexor for the extended request to send message - static constexpr std::uint8_t EXTENDED_CLEAR_TO_SEND_MULTIPLEXOR = 0x15; ///< The multiplexor for the extended clear to send message - static constexpr std::uint8_t EXTENDED_DATA_PACKET_OFFSET_MULTIPLEXOR = 0x16; ///< The multiplexor for the extended data packet offset message - static constexpr std::uint8_t EXTENDED_END_OF_MESSAGE_ACKNOWLEDGEMENT = 0x17; ///< Multiplexor for the extended end of message acknowledgement message - static constexpr std::uint8_t EXTENDED_CONNECTION_ABORT_MULTIPLEXOR = 0xFF; ///< Multiplexor for the extended connection abort message - static constexpr std::uint8_t PROTOCOL_BYTES_PER_FRAME = 7; ///< The number of payload bytes per frame minus overhead of sequence number - static constexpr std::uint8_t SEQUENCE_NUMBER_DATA_INDEX = 0; ///< The index of the sequence number in a frame - /// @brief Aborts the session with the specified abort reason. Sends a CAN message. /// @param[in] session The session to abort /// @param[in] reason The reason we're aborting the session /// @returns true if the abort was send OK, false if not sent - bool abort_session(ExtendedTransportProtocolSession *session, ConnectionAbortReason reason); + bool abort_session(const ExtendedTransportProtocolSession &session, ConnectionAbortReason reason); - /// @brief Aborts Tp with no corresponding session with the specified abort reason. Sends a CAN message. + /// @brief Send an abort with no corresponding session with the specified abort reason. Sends a CAN message. + /// @param[in] sender The sender of the abort + /// @param[in] receiver The receiver of the abort /// @param[in] parameterGroupNumber The PGN of the ETP "session" we're aborting /// @param[in] reason The reason we're aborting the session - /// @param[in] source The source control function from which we'll send the abort - /// @param[in] destination The destination control function to which we'll send the abort - /// @returns true if the abort was sent OK, false if not sent - bool abort_session(std::uint32_t parameterGroupNumber, ConnectionAbortReason reason, std::shared_ptr source, std::shared_ptr destination); + /// @returns true if the abort was send OK, false if not sent + bool send_abort(std::shared_ptr sender, + std::shared_ptr receiver, + std::uint32_t parameterGroupNumber, + ConnectionAbortReason reason) const; /// @brief Gracefully closes a session to prepare for a new session /// @param[in] session The session to close - /// @param[in] successfull True if the session was closed successfully, false if not - void close_session(ExtendedTransportProtocolSession *session, bool successfull); - - /// @brief Gets an ETP session from the passed in source and destination combination - /// @param[in] source The source control function for the session - /// @param[in] destination The destination control function for the session - /// @param[out] session The found session, or nullptr if no session matched the supplied parameters - /// @returns true if a matching session was found, false if not - bool get_session(ExtendedTransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination) const; - - /// @brief Gets an ETP session from the passed in source and destination and PGN combination - /// @param[in] source The source control function for the session - /// @param[in] destination The destination control function for the session - /// @param[in] parameterGroupNumber The PGN of the session - /// @param[out] session The found session, or nullptr if no session matched the supplied parameters - /// @returns true if a matching session was found, false if not - bool get_session(ExtendedTransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination, std::uint32_t parameterGroupNumber) const; - - /// @brief Processes end of session callbacks - /// @param[in] session The session we've just completed - /// @param[in] success Denotes if the session was successful - void process_session_complete_callback(ExtendedTransportProtocolSession *session, bool success); + /// @param[in] successful Denotes if the session was successful + void close_session(const ExtendedTransportProtocolSession &session, bool successful); - /// @brief Sends the "end of message acknowledgement" message for the provided session - /// @param[in] session The session for which we're sending the EOM ACK - /// @returns true if the EOM was sent, false if sending was not successful - bool send_end_of_session_acknowledgement(ExtendedTransportProtocolSession *session) const; // ETP.CM_EOMA + /// @brief Sends the "request to send" message as part of initiating a transmit + /// @param[in] session The session for which we're sending the RTS + /// @returns true if the RTS was sent, false if sending was not successful + bool send_request_to_send(const ExtendedTransportProtocolSession &session) const; /// @brief Sends the "clear to send" message /// @param[in] session The session for which we're sending the CTS /// @returns true if the CTS was sent, false if sending was not successful - bool send_extended_connection_mode_clear_to_send(ExtendedTransportProtocolSession *session) const; // ETP.CM_CTS - - /// @brief Sends the "request to send" message as part of initiating a transmit - /// @param[in] session The session for which we're sending the RTS - /// @returns true if the RTS was sent, false if sending was not successful - bool send_extended_connection_mode_request_to_send(const ExtendedTransportProtocolSession *session) const; // ETP.CM_RTS + bool send_clear_to_send(ExtendedTransportProtocolSession &session) const; - /// @brief Sends the data packet offset message for the supplied session - /// @param[in] session The session for which we're sending the EDPO - /// @returns true if the EDPO was sent, false if sending was not successful - bool send_extended_connection_mode_data_packet_offset(const ExtendedTransportProtocolSession *session) const; // ETP.CM_DPO + /// @brief Sends the "data packet offset" message for the provided session + /// @param[in] session The session for which we're sending the DPO + /// @returns true if the DPO was sent, false if sending was not successful + bool send_data_packet_offset(ExtendedTransportProtocolSession &session) const; - /// @brief Sets the state machine state of the ETP session - /// @param[in] session The session to update - /// @param[in] value The state to update the session to - void set_state(ExtendedTransportProtocolSession *session, StateMachineState value); + /// @brief Sends the "end of message acknowledgement" message for the provided session + /// @param[in] session The session for which we're sending the EOM ACK + /// @returns true if the EOM was sent, false if sending was not successful + bool send_end_of_session_acknowledgement(const ExtendedTransportProtocolSession &session) const; + + ///@brief Sends data transfer packets for the specified ExtendedTransportProtocolSession. + /// @param[in] session The ExtendedTransportProtocolSession for which to send data transfer packets. + void send_data_transfer_packets(ExtendedTransportProtocolSession &session) const; + + /// @brief Processes a request to send a message over the CAN transport protocol. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The Parameter Group Number of the message. + /// @param[in] totalMessageSize The total size of the message in bytes. + /// @param[in] totalNumberOfPackets The total number of packets to be sent. + /// @param[in] clearToSendPacketMax The maximum number of clear to send packets that can be sent. + void process_request_to_send(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint32_t totalMessageSize); + + /// @brief Processes the Clear To Send (CTS) message. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The Parameter Group Number (PGN) of the message. + /// @param[in] packetsToBeSent The number of packets to be sent. + /// @param[in] nextPacketNumber The next packet number. + void process_clear_to_send(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint8_t packetsToBeSent, std::uint32_t nextPacketNumber); + + /// @brief Processes the Data Packet Offset (DPO) message. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The Parameter Group Number (PGN) of the message. + /// @param[in] numberOfPackets The number of packets that will follow. + /// @param[in] packetOffset The packet offset (always 1 less than CTS next packet number) + void process_data_packet_offset(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint8_t numberOfPackets, std::uint32_t packetOffset); + + /// @brief Processes the end of session acknowledgement. + /// @param[in] source The source control function. + /// @param[in] destination The destination control function. + /// @param[in] parameterGroupNumber The parameter group number. + void process_end_of_session_acknowledgement(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint32_t numberOfBytesTransferred); + + /// @brief Processes an abort message in the CAN transport protocol. + /// @param[in] source The shared pointer to the source control function. + /// @param[in] destination The shared pointer to the destination control function. + /// @param[in] parameterGroupNumber The PGN (Parameter Group Number) of the message. + /// @param[in] reason The reason for the connection abort. + void process_abort(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, ExtendedTransportProtocolManager::ConnectionAbortReason reason); + + /// @brief Processes a connection management message. + /// @param[in] message The CAN message to be processed. + void process_connection_management_message(const CANMessage &message); + + /// @brief Processes a data transfer message. + /// @param[in] message The CAN message to be processed. + void process_data_transfer_message(const CANMessage &message); + + /// @brief Gets a ETP session from the passed in source and destination and PGN combination + /// @param[in] source The source control function for the session + /// @param[in] destination The destination control function for the session + /// @returns a matching session, or nullptr if no session matched the supplied parameters + ExtendedTransportProtocolSession *get_session(std::shared_ptr source, std::shared_ptr destination); - /// @brief Updates the state machine of a ETP session + /// @brief Update the state machine for the passed in session /// @param[in] session The session to update - void update_state_machine(ExtendedTransportProtocolSession *session); + void update_state_machine(ExtendedTransportProtocolSession &session); - std::vector activeSessions; ///< A list of all active TP sessions + std::vector activeSessions; ///< A list of all active ETP sessions + const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame + const CANMessageCallback canMessageReceivedCallback; ///< A callback for when a complete CAN message is received using the ETP protocol + const CANNetworkConfiguration *configuration; ///< The configuration to use for this protocol }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/can_network_manager.hpp b/isobus/include/isobus/isobus/can_network_manager.hpp index 3ad64cd3..7c37226d 100644 --- a/isobus/include/isobus/isobus/can_network_manager.hpp +++ b/isobus/include/isobus/isobus/can_network_manager.hpp @@ -371,9 +371,9 @@ namespace isobus static constexpr std::uint32_t BUSLOAD_UPDATE_FREQUENCY_MS = 100; ///< Bus load bit accumulation happens over a 100ms window CANNetworkConfiguration configuration; ///< The configuration for this network manager + TransportProtocolManager transportProtocol; ///< Instance of the transport protocol manager ExtendedTransportProtocolManager extendedTransportProtocol; ///< Instance of the extended transport protocol manager FastPacketProtocol fastPacketProtocol; ///< Instance of the fast packet protocol - TransportProtocolManager transportProtocol; ///< Instance of the transport protocol manager std::array, CAN_PORT_MAXIMUM> busloadMessageBitsHistory; ///< Stores the approximate number of bits processed on each channel over multiple previous time windows std::array currentBusloadBitAccumulator; ///< Accumulates the approximate number of bits processed on each channel during the current time window diff --git a/isobus/include/isobus/isobus/can_transport_protocol.hpp b/isobus/include/isobus/isobus/can_transport_protocol.hpp index 2a5d5668..343cbef2 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol.hpp @@ -18,8 +18,6 @@ #include "isobus/isobus/can_message_frame.hpp" #include "isobus/isobus/can_network_configuration.hpp" -#include - namespace isobus { //================================================================================================ @@ -42,13 +40,29 @@ namespace isobus enum class StateMachineState { None, ///< Protocol session is not in progress - ClearToSend, ///< We are sending clear to send message - RxDataSession, ///< Rx data session is in progress - RequestToSend, ///< We are sending the request to send message + SendBroadcastAnnounce, ///< We are sending the broadcast announce message (BAM) + SendRequestToSend, ///< We are sending the request to send message WaitForClearToSend, ///< We are waiting for a clear to send message - BroadcastAnnounce, ///< We are sending the broadcast announce message (BAM) - TxDataSession, ///< A Tx data session is in progress - WaitForEndOfMessageAcknowledge ///< We are waiting for an end of message acknowledgement + SendClearToSend, ///< We are sending clear to send message + WaitForDataTransferPacket, ///< We are waiting for data transfer packets + SendDataTransferPackets, ///< A Tx data session is in progress + WaitForEndOfMessageAcknowledge, ///< We are waiting for an end of message acknowledgement + }; + + /// @brief A list of all defined abort reasons in ISO11783 + enum class ConnectionAbortReason : std::uint8_t + { + Reserved = 0, ///< Reserved, not to be used, but should be tolerated + AlreadyInCMSession = 1, ///< We are already in a connection mode session and can't support another + SystemResourcesNeeded = 2, ///< Session must be aborted because the system needs resources + Timeout = 3, ///< General timeout + ClearToSendReceivedWhileTransferInProgress = 4, ///< A CTS was received while already processing the last CTS + MaximumRetransmitRequestLimitReached = 5, ///< Maximum retries for the data has been reached + UnexpectedDataTransferPacketReceived = 6, ///< A data packet was received outside the proper state + BadSequenceNumber = 7, ///< Incorrect sequence number was received and cannot be recovered + DuplicateSequenceNumber = 8, ///< Re-received a sequence number we've already processed + TotalMessageSizeTooBig = 9, ///< TP Can't support a message this large (>1785 bytes) + AnyOtherError = 250 ///< Any reason not defined in the standard }; //================================================================================================ @@ -216,7 +230,7 @@ namespace isobus Direction direction; ///< The direction of the session std::uint32_t parameterGroupNumber; ///< The PGN of the message std::unique_ptr data; ///< The data buffer for the message - std::uint32_t totalMessageSize; ///< The total size of the message in bytes + std::uint16_t totalMessageSize; ///< The total size of the message in bytes std::shared_ptr source; ///< The source control function std::shared_ptr destination; ///< The destination control function @@ -232,34 +246,11 @@ namespace isobus void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr }; - /// @brief A list of all defined abort reasons in ISO11783 - enum class ConnectionAbortReason : std::uint8_t - { - Reserved = 0, ///< Reserved, not to be used, but should be tolerated - AlreadyInCMSession = 1, ///< We are already in a connection mode session and can't support another - SystemResourcesNeeded = 2, ///< Session must be aborted because the system needs resources - Timeout = 3, ///< General timeout - ClearToSendReceivedWhileTransferInProgress = 4, ///< A CTS was received while already processing the last CTS - MaximumRetransmitRequestLimitReached = 5, ///< Maximum retries for the data has been reached - UnexpectedDataTransferPacketReceived = 6, ///< A data packet was received outside the proper state - BadSequenceNumber = 7, ///< Incorrect sequence number was received and cannot be recovered - DuplicateSequenceNumber = 8, ///< Re-received a sequence number we've already processed - TotalMessageSizeTooBig = 9, ///< TP Can't support a message this large (>1785 bytes) - AnyOtherError = 250 ///< Any other error not enumerated above, 0xFE - }; - - using SendCANFrameCallback = std::function sourceControlFunction, - std::shared_ptr destinationControlFunction, - CANIdentifier::CANPriority priority)>; ///< A callback for sending a CAN frame - using CANMessageReceivedCallback = std::function; ///< A callback for when a complete CAN message is received using the TP protocol - - static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 0x10; ///< TP.CM_RTS Multiplexor - static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 0x11; ///< TP.CM_CTS Multiplexor - static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 0x13; ///< TP.CM_EOM_ACK Multiplexor - static constexpr std::uint32_t BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR = 0x20; ///< TP.BAM Multiplexor - static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 0xFF; ///< Abort multiplexor + static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 16; ///< TP.CM_RTS Multiplexor + static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 17; ///< TP.CM_CTS Multiplexor + static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 19; ///< TP.CM_EOM_ACK Multiplexor + static constexpr std::uint32_t BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR = 32; ///< TP.BAM Multiplexor + static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 255; ///< Abort multiplexor static constexpr std::uint32_t MAX_PROTOCOL_DATA_LENGTH = 1785; ///< The max number of bytes that this protocol can transfer static constexpr std::uint32_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard static constexpr std::uint32_t T2_T3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard @@ -273,8 +264,8 @@ namespace isobus /// @param[in] sendCANFrameCallback A callback for sending a CAN frame to hardware /// @param[in] canMessageReceivedCallback A callback for when a complete CAN message is received using the TP protocol /// @param[in] configuration The configuration to use for this protocol - TransportProtocolManager(const SendCANFrameCallback &sendCANFrameCallback, - const CANMessageReceivedCallback &canMessageReceivedCallback, + TransportProtocolManager(const CANMessageFrameCallback &sendCANFrameCallback, + const CANMessageCallback &canMessageReceivedCallback, const CANNetworkConfiguration *configuration); /// @brief Updates all sessions managed by this protocol manager instance. @@ -333,16 +324,16 @@ namespace isobus /// @returns true if the BAM was sent, false if sending was not successful bool send_broadcast_announce_message(const TransportProtocolSession &session) const; - /// @brief Sends the "clear to send" message - /// @param[in] session The session for which we're sending the CTS - /// @returns true if the CTS was sent, false if sending was not successful - bool send_clear_to_send(TransportProtocolSession &session) const; - /// @brief Sends the "request to send" message as part of initiating a transmit /// @param[in] session The session for which we're sending the RTS /// @returns true if the RTS was sent, false if sending was not successful bool send_request_to_send(const TransportProtocolSession &session) const; + /// @brief Sends the "clear to send" message + /// @param[in] session The session for which we're sending the CTS + /// @returns true if the CTS was sent, false if sending was not successful + bool send_clear_to_send(TransportProtocolSession &session) const; + /// @brief Sends the "end of message acknowledgement" message for the provided session /// @param[in] session The session for which we're sending the EOM ACK /// @returns true if the EOM was sent, false if sending was not successful @@ -408,8 +399,8 @@ namespace isobus void update_state_machine(TransportProtocolSession &session); std::vector activeSessions; ///< A list of all active TP sessions - const SendCANFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame - const CANMessageReceivedCallback canMessageReceivedCallback; ///< A callback for when a complete CAN message is received using the TP protocol + const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame + const CANMessageCallback canMessageReceivedCallback; ///< A callback for when a complete CAN message is received using the TP protocol const CANNetworkConfiguration *configuration; ///< The configuration to use for this protocol }; diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index 3c8de1c7..45d584ef 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -3,6 +3,7 @@ /// /// @brief A protocol class that handles the ISO11783 extended transport protocol. /// @author Adrian Del Grosso +/// @author Daan Steenbergen /// /// @copyright 2022 Adrian Del Grosso //================================================================================================ @@ -10,825 +11,1002 @@ #include "isobus/isobus/can_extended_transport_protocol.hpp" #include "isobus/isobus/can_general_parameter_group_numbers.hpp" -#include "isobus/isobus/can_network_configuration.hpp" -#include "isobus/isobus/can_network_manager.hpp" +#include "isobus/isobus/can_internal_control_function.hpp" +#include "isobus/isobus/can_message.hpp" #include "isobus/isobus/can_stack_logger.hpp" #include "isobus/utility/system_timing.hpp" #include "isobus/utility/to_string.hpp" #include +#include namespace isobus { - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::ExtendedTransportProtocolSession(Direction sessionDirection, std::uint8_t canPortIndex) : - sessionMessage(canPortIndex), - sessionDirection(sessionDirection) + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::ExtendedTransportProtocolSession(Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize, + std::uint32_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) : + direction(direction), + parameterGroupNumber(parameterGroupNumber), + data(std::move(data)), + totalMessageSize(totalMessageSize), + source(source), + destination(destination), + totalNumberOfPackets(totalNumberOfPackets), + clearToSendPacketCountMax(clearToSendPacketMax), + sessionCompleteCallback(sessionCompleteCallback), + parent(parentPointer) { } - bool ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::operator==(const ExtendedTransportProtocolSession &obj) + bool ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::operator==(const ExtendedTransportProtocolSession &obj) const { - return ((sessionMessage.get_source_control_function() == obj.sessionMessage.get_source_control_function()) && - (sessionMessage.get_destination_control_function() == obj.sessionMessage.get_destination_control_function()) && - (sessionMessage.get_identifier().get_parameter_group_number() == obj.sessionMessage.get_identifier().get_parameter_group_number())); + return ((source == obj.source) && (destination == obj.destination) && (parameterGroupNumber == obj.parameterGroupNumber)); } - std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_message_data_length() const + bool ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::matches(std::shared_ptr other_source, std::shared_ptr other_destination) const { - if (nullptr != frameChunkCallback) - { - return frameChunkCallbackMessageLength; - } - return sessionMessage.get_data_length(); + return ((source == other_source) && (destination == other_destination)); + } + + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::Direction ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_direction() const + { + return direction; + } + + ExtendedTransportProtocolManager::StateMachineState ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_state() const + { + return state; + } + + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_message_length() const + { + return totalMessageSize; } - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::~ExtendedTransportProtocolSession() + CANMessageData &ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_data() const { + return *data; } - ExtendedTransportProtocolManager::ExtendedTransportProtocolManager() + std::shared_ptr ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_source() const { + return source; } - ExtendedTransportProtocolManager ::~ExtendedTransportProtocolManager() + std::shared_ptr ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_destination() const { - // No need to clean up, as this object is a member of the network manager - // so its callbacks will be cleared at destruction time + return destination; } - void ExtendedTransportProtocolManager::initialize(CANLibBadge) + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_parameter_group_number() const { - if (!initialized) + return parameterGroupNumber; + } + + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::create_receive_session(std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize, + std::uint32_t totalNumberOfPackets, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination) + { + return ExtendedTransportProtocolSession(ExtendedTransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + clearToSendPacketMax, + source, + destination, + nullptr, + nullptr); + } + + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::create_transmit_session(std::uint32_t parameterGroupNumber, + std::unique_ptr data, + std::shared_ptr source, + std::shared_ptr destination, + std::uint8_t clearToSendPacketMax, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) + { + auto totalMessageSize = static_cast(data->size()); + auto totalNumberOfPackets = totalMessageSize / PROTOCOL_BYTES_PER_FRAME; + // Because integer division rounds down, we need to add one if there is a remainder + if (0 != (totalMessageSize % PROTOCOL_BYTES_PER_FRAME)) { - initialized = true; - CANNetworkManager::CANNetwork.add_protocol_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolDataTransfer), process_message, this); - CANNetworkManager::CANNetwork.add_protocol_parameter_group_number_callback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), process_message, this); + totalNumberOfPackets++; } + return ExtendedTransportProtocolSession(ExtendedTransportProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + clearToSendPacketMax, + source, + destination, + sessionCompleteCallback, + parentPointer); } - void ExtendedTransportProtocolManager::process_message(const CANMessage &message) + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_state(StateMachineState value) + { + state = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_dpo_number_of_packets_remaining() const + { + auto packetsSinceDPO = static_cast(get_last_packet_number() - lastAcknowledgedPacketNumber); + return clearToSendPacketCount - packetsSinceDPO; + } + + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_dpo_number_of_packets(std::uint8_t value) + { + clearToSendPacketCount = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_dpo_number_of_packets() const + { + return clearToSendPacketCount; + } + + std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_cts_number_of_packet_limit() const + { + return clearToSendPacketCountMax; + } + + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_cts_number_of_packet_limit(std::uint8_t value) { - if ((nullptr != CANNetworkManager::CANNetwork.get_internal_control_function(message.get_destination_control_function()))) + clearToSendPacketCountMax = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_last_sequence_number() const + { + return lastSequenceNumber; + } + + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_last_packet_number() const + { + return lastSequenceNumber + sequenceNumberOffset; + } + + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_last_sequency_number(std::uint8_t value) + { + lastSequenceNumber = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_acknowledged_packet_number(std::uint32_t value) + { + lastAcknowledgedPacketNumber = value; + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_last_acknowledged_packet_number() const + { + return lastAcknowledgedPacketNumber; + } + + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_number_of_remaining_packets() const + { + return totalNumberOfPackets - get_last_packet_number(); + } + + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_total_number_of_packets() const + { + return totalNumberOfPackets; + } + + ExtendedTransportProtocolManager::ExtendedTransportProtocolManager(const CANMessageFrameCallback &sendCANFrameCallback, + const CANMessageCallback &canMessageReceivedCallback, + const CANNetworkConfiguration *configuration) : + sendCANFrameCallback(sendCANFrameCallback), + canMessageReceivedCallback(canMessageReceivedCallback), + configuration(configuration) + { + } + + void ExtendedTransportProtocolManager::process_request_to_send(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize) + { + if (activeSessions.size() >= configuration->get_max_number_transport_protocol_sessions()) { - switch (message.get_identifier().get_parameter_group_number()) + // TODO: consider using maximum memory instead of maximum number of sessions + CANStackLogger::warn("[ETP]: Replying with abort to Request To Send (RTS) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); + send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::AlreadyInCMSession); + } + else + { + auto oldSession = get_session(source, destination); + if (nullptr != oldSession) { - case static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement): + if (oldSession->get_parameter_group_number() != parameterGroupNumber) { - if (CAN_DATA_LENGTH == message.get_data_length()) - { - ExtendedTransportProtocolSession *session; - const auto &data = message.get_data(); - const std::uint32_t pgn = (static_cast(data[5]) | (static_cast(data[6]) << 8) | (static_cast(data[7]) << 16)); - - switch (message.get_data()[0]) - { - case EXTENDED_REQUEST_TO_SEND_MULTIPLEXOR: - { - if ((nullptr != message.get_destination_control_function()) && - (activeSessions.size() < CANNetworkManager::CANNetwork.get_configuration().get_max_number_transport_protocol_sessions()) && - (!get_session(session, message.get_source_control_function(), message.get_destination_control_function(), pgn))) - { - ExtendedTransportProtocolSession *newSession = new ExtendedTransportProtocolSession(ExtendedTransportProtocolSession::Direction::Receive, message.get_can_port_index()); - CANIdentifier tempIdentifierData(CANIdentifier::Type::Extended, pgn, CANIdentifier::CANPriority::PriorityLowest7, message.get_destination_control_function()->get_address(), message.get_source_control_function()->get_address()); - newSession->sessionMessage.set_data_size(static_cast(data[1]) | static_cast(data[2] << 8) | static_cast(data[3] << 16) | static_cast(data[4] << 24)); - newSession->sessionMessage.set_source_control_function(message.get_source_control_function()); - newSession->sessionMessage.set_destination_control_function(message.get_destination_control_function()); - newSession->packetCount = 0xFF; - newSession->sessionMessage.set_identifier(tempIdentifierData); - newSession->state = StateMachineState::ClearToSend; - newSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - activeSessions.push_back(newSession); - } - else if ((get_session(session, message.get_source_control_function(), message.get_destination_control_function(), pgn)) && - (nullptr != message.get_destination_control_function()) && - (ControlFunction::Type::Internal == message.get_destination_control_function()->get_type())) - { - abort_session(pgn, ConnectionAbortReason::AlreadyInConnectionManagedSessionAndCannotSupportAnother, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " RTS when already in session"); - close_session(session, false); - } - else if ((activeSessions.size() >= CANNetworkManager::CANNetwork.get_configuration().get_max_number_transport_protocol_sessions()) && - (nullptr != message.get_destination_control_function()) && - (ControlFunction::Type::Internal == message.get_destination_control_function()->get_type())) - { - abort_session(pgn, ConnectionAbortReason::SystemResourcesNeededForAnotherTask, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " No Sessions Available"); - close_session(session, false); - } - } - break; - - case EXTENDED_CLEAR_TO_SEND_MULTIPLEXOR: - { - const std::uint8_t packetsToBeSent = data[1]; - - if (get_session(session, message.get_destination_control_function(), message.get_source_control_function(), pgn)) - { - if (StateMachineState::WaitForClearToSend == session->state) - { - session->packetCount = packetsToBeSent; - - if (session->packetCount > CANNetworkManager::CANNetwork.get_configuration().get_max_number_of_etp_frames_per_edpo()) - { - session->packetCount = CANNetworkManager::CANNetwork.get_configuration().get_max_number_of_etp_frames_per_edpo(); - } - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - // If 0 was sent as the packet number, they want us to wait. - // Just sit here in this state until we get a non-zero packet count - if (0 != packetsToBeSent) - { - session->lastPacketNumber = 0; - session->state = StateMachineState::TxDataSession; - } - } - else - { - // The session exists, but we're probably already in the TxDataSession state. Need to abort - // In the case of Rx'ing a CTS, we're the source in the session - abort_session(pgn, ConnectionAbortReason::ClearToSendReceivedWhenDataTransferInProgress, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " CTS while in data session"); - close_session(session, false); - } - } - else - { - // We got a CTS but no session exists. Aborting clears up the situation faster than waiting for them to timeout - // In the case of Rx'ing a CTS, we're the source in the session - abort_session(pgn, ConnectionAbortReason::AnyOtherReason, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " CTS With no matching session"); - } - } - break; - - case EXTENDED_DATA_PACKET_OFFSET_MULTIPLEXOR: - { - const std::uint32_t dataPacketOffset = (static_cast(data[2]) | (static_cast(data[3]) << 8) | (static_cast(data[4]) << 16)); - - if (get_session(session, message.get_source_control_function(), message.get_destination_control_function(), pgn)) - { - const std::uint8_t packetsToBeSent = data[1]; - - if (packetsToBeSent != session->packetCount) - { - if (packetsToBeSent > session->packetCount) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " DPO packet count is greater than CTS"); - abort_session(session, ConnectionAbortReason::EDPONumberOfPacketsGreaterThanClearToSend); - close_session(session, false); - } - else - { - /// @note If byte 2 is less than byte 2 of the ETP.CM_CTS message, then the receiver shall make - /// necessary adjustments to its session to accept the data block defined by the - /// ETP.CM_DPO message and the subsequent ETP.DT packets. - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[ETP]: DPO packet count disagrees with CTS. Using DPO value."); - session->packetCount = packetsToBeSent; - } - } - - if (dataPacketOffset == session->processedPacketsThisSession) - { - // All is good. Proceed with message. - session->lastPacketNumber = 0; - set_state(session, StateMachineState::RxDataSession); - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " DPO packet offset is not valid"); - abort_session(session, ConnectionAbortReason::BadEDPOOffset); - close_session(session, false); - } - } - else - { - bool anySessionMatched = false; - // Do we have any session that matches except for PGN? - for (auto currentSession : activeSessions) - { - if ((currentSession->sessionMessage.get_source_control_function() == message.get_source_control_function()) && - (currentSession->sessionMessage.get_destination_control_function() == message.get_destination_control_function())) - { - // Sending EDPO for this session with mismatched PGN is not allowed - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " EDPO for this session with mismatched PGN is not allowed"); - abort_session(currentSession, ConnectionAbortReason::UnexpectedEDPOPgn); - close_session(session, false); - anySessionMatched = true; - break; - } - } - - if (!anySessionMatched) - { - abort_session(pgn, ConnectionAbortReason::UnexpectedEDPOPacket, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - } - } - } - break; - - case EXTENDED_END_OF_MESSAGE_ACKNOWLEDGEMENT: - { - if ((nullptr != message.get_destination_control_function()) && - (nullptr != message.get_source_control_function())) - { - if (get_session(session, message.get_destination_control_function(), message.get_source_control_function(), pgn)) - { - if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) - { - // We completed our Tx session! - session->state = StateMachineState::None; - close_session(session, true); - } - else - { - abort_session(pgn, ConnectionAbortReason::AnyOtherReason, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - close_session(session, false); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " received EOM in wrong session state"); - } - } - else - { - abort_session(pgn, ConnectionAbortReason::AnyOtherReason, std::static_pointer_cast(message.get_destination_control_function()), message.get_source_control_function()); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Sent abort to address " + isobus::to_string(static_cast(message.get_source_control_function()->get_address())) + " EOM without matching session"); - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[ETP]: Bad EOM received, sent to or from an invalid control function"); - } - } - break; - - case EXTENDED_CONNECTION_ABORT_MULTIPLEXOR: - { - if (get_session(session, message.get_destination_control_function(), message.get_source_control_function(), pgn)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Received an abort for an session with PGN: " + isobus::to_string(pgn)); - close_session(session, false); - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Received an abort with no matching session with PGN: " + isobus::to_string(pgn)); - } - } - break; - - default: - { - } - break; - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[ETP]: Received an invalid ETP CM frame"); - } + CANStackLogger::error("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", parameterGroupNumber); + abort_session(*oldSession, ConnectionAbortReason::AlreadyInCMSession); } - break; - - case static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolDataTransfer): + else { - ExtendedTransportProtocolSession *tempSession = nullptr; - auto &messageData = message.get_data(); - - if ((CAN_DATA_LENGTH == message.get_data_length()) && - (get_session(tempSession, message.get_source_control_function(), message.get_destination_control_function())) && - (StateMachineState::RxDataSession == tempSession->state) && - (messageData[SEQUENCE_NUMBER_DATA_INDEX] == (tempSession->lastPacketNumber + 1))) - { - for (std::uint8_t i = SEQUENCE_NUMBER_DATA_INDEX; (i < PROTOCOL_BYTES_PER_FRAME) && (((PROTOCOL_BYTES_PER_FRAME * tempSession->processedPacketsThisSession) + i) < tempSession->get_message_data_length()); i++) - { - std::uint32_t currentDataIndex = (PROTOCOL_BYTES_PER_FRAME * tempSession->processedPacketsThisSession) + i; - tempSession->sessionMessage.set_data(messageData[1 + SEQUENCE_NUMBER_DATA_INDEX + i], currentDataIndex); - } - tempSession->lastPacketNumber++; - tempSession->processedPacketsThisSession++; - if ((tempSession->processedPacketsThisSession * PROTOCOL_BYTES_PER_FRAME) >= tempSession->get_message_data_length()) - { - if (nullptr != tempSession->sessionMessage.get_destination_control_function()) - { - send_end_of_session_acknowledgement(tempSession); - } - CANNetworkManager::CANNetwork.protocol_message_callback(tempSession->sessionMessage); - close_session(tempSession, true); - } - tempSession->timestamp_ms = SystemTiming::get_timestamp_ms(); - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Warning, "[ETP]: Received an unexpected or invalid data transfer frame"); - } + CANStackLogger::warn("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", parameterGroupNumber); + close_session(*oldSession, false); } - break; } + + auto data = std::unique_ptr(new CANMessageDataVector(totalMessageSize)); + auto totalNumberOfPackets = totalMessageSize / PROTOCOL_BYTES_PER_FRAME; + // Because integer division rounds down, we need to add one if there is a remainder + if (0 != (totalMessageSize % PROTOCOL_BYTES_PER_FRAME)) + { + totalNumberOfPackets++; + } + + ExtendedTransportProtocolSession newSession = ExtendedTransportProtocolSession::create_receive_session(parameterGroupNumber, + totalMessageSize, + totalNumberOfPackets, + 0xFF, + source, + destination); + newSession.set_state(StateMachineState::SendClearToSend); + activeSessions.push_back(std::move(newSession)); } } - void ExtendedTransportProtocolManager::process_message(const CANMessage &message, void *parent) + void ExtendedTransportProtocolManager::process_clear_to_send(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + std::uint8_t packetsToBeSent, + std::uint32_t nextPacketNumber) { - if (nullptr != parent) + auto session = get_session(destination, source); + if (nullptr != session) { - reinterpret_cast(parent)->process_message(message); + if (session->get_parameter_group_number() != parameterGroupNumber) + { + CANStackLogger::error("[ETP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + } + else if (nextPacketNumber > session->get_total_number_of_packets()) + { + CANStackLogger::error("[ETP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::NumberOfClearToSendPacketsExceedsMessage); + } + else if (StateMachineState::WaitForClearToSend != session->state) + { + // The session exists, but we're not in the right state to receive a CTS, so we must abort + CANStackLogger::warn("[ETP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + } + else + { + session->set_acknowledged_packet_number(nextPacketNumber - 1); + session->set_cts_number_of_packet_limit(packetsToBeSent); + + // If 0 was sent as the packet number, they want us to wait. + // Just sit here in this state until we get a non-zero packet count + if (0 != packetsToBeSent) + { + session->set_state(StateMachineState::SendDataPacketOffset); + } + } + } + else + { + // We got a CTS but no session exists, by the standard we must ignore it + CANStackLogger::warn("[ETP]: Received Clear To Send (CTS) for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); } } - bool ExtendedTransportProtocolManager::protocol_transmit_message(std::uint32_t parameterGroupNumber, - const std::uint8_t *dataBuffer, - std::uint32_t messageLength, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer, - DataChunkCallback frameChunkCallback) + void ExtendedTransportProtocolManager::process_data_packet_offset(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + std::uint8_t numberOfPackets, + std::uint32_t packetOffset) { - ExtendedTransportProtocolSession *session; - bool retVal = false; - - if ((messageLength < MAX_PROTOCOL_DATA_LENGTH) && - (messageLength >= MIN_PROTOCOL_DATA_LENGTH) && - (nullptr != destination) && - ((nullptr != dataBuffer) || - (nullptr != frameChunkCallback)) && - (nullptr != source) && - (true == source->get_address_valid()) && - (destination->get_address_valid()) && - (!get_session(session, source, destination, parameterGroupNumber))) + auto session = get_session(destination, source); + if (nullptr != session) { - ExtendedTransportProtocolSession *newSession = new ExtendedTransportProtocolSession(ExtendedTransportProtocolSession::Direction::Transmit, - source->get_can_port()); - - if (dataBuffer != nullptr) + if (session->get_parameter_group_number() != parameterGroupNumber) { - newSession->sessionMessage.set_data(dataBuffer, messageLength); + CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X while a session already existed for this source and destination with a different PGN, sending abort for both...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); + send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); } - else + else if (StateMachineState::WaitForClearToSend != session->state) + { + // The session exists, but we're not in the right state to receive a DPO, so we must abort + CANStackLogger::warn("[ETP]: Received a Data Packet Offset message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::UnexpectedDataPacketOffsetReceived); + } + else if (packetOffset > session->get_cts_number_of_packet_limit()) { - newSession->frameChunkCallback = frameChunkCallback; - newSession->frameChunkCallbackMessageLength = messageLength; + CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with a different number of packets than our CTS, aborting...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::DataPacketOffsetExceedsClearToSend); } - newSession->sessionMessage.set_source_control_function(source); - newSession->sessionMessage.set_destination_control_function(destination); - newSession->packetCount = (messageLength / PROTOCOL_BYTES_PER_FRAME); - newSession->lastPacketNumber = 0; - newSession->processedPacketsThisSession = 0; - newSession->sessionCompleteCallback = sessionCompleteCallback; - newSession->parent = parentPointer; - if (0 != (messageLength % PROTOCOL_BYTES_PER_FRAME)) + else if (packetOffset != (session->get_last_acknowledged_packet_number() - 1)) { - newSession->packetCount++; + CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); + abort_session(*session, ConnectionAbortReason::BadDataPacketOffset); } - CANIdentifier messageVirtualID(CANIdentifier::Type::Extended, - parameterGroupNumber, - CANIdentifier::CANPriority::PriorityDefault6, - destination->get_address(), - source->get_address()); + else + { + session->set_dpo_number_of_packets(numberOfPackets); - newSession->sessionMessage.set_identifier(messageVirtualID); - set_state(newSession, StateMachineState::RequestToSend); - activeSessions.push_back(newSession); - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Debug, "[ETP]: New ETP Session. Dest: " + isobus::to_string(static_cast(destination->get_address()))); - retVal = true; + // If 0 was sent as the packet number, they want us to wait. + // Just sit here in this state until we get a non-zero packet count + if (0 != numberOfPackets) + { + session->set_state(StateMachineState::WaitForDataTransferPacket); + } + } } - return retVal; - } - - void ExtendedTransportProtocolManager::update(CANLibBadge) - { - for (auto i : activeSessions) + else { - update_state_machine(i); + // We got a CTS but no session exists, by the standard we must ignore it + CANStackLogger::warn("[ETP]: Received Data Packet Offset for 0x%05X while no session existed for this source and destination, ignoring...", parameterGroupNumber); } } - bool ExtendedTransportProtocolManager::abort_session(ExtendedTransportProtocolSession *session, ConnectionAbortReason reason) + void ExtendedTransportProtocolManager::process_end_of_session_acknowledgement(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + std::uint32_t) { - bool retVal = false; - + auto session = get_session(destination, source); if (nullptr != session) { - std::shared_ptr myControlFunction; - std::shared_ptr partnerControlFunction; - std::array data; - std::uint32_t pgn = session->sessionMessage.get_identifier().get_parameter_group_number(); - - if (ExtendedTransportProtocolSession::Direction::Transmit == session->sessionDirection) + if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) { - myControlFunction = CANNetworkManager::CANNetwork.get_internal_control_function(session->sessionMessage.get_source_control_function()); - partnerControlFunction = session->sessionMessage.get_destination_control_function(); + CANStackLogger::debug("[ETP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); + session->state = StateMachineState::None; + close_session(*session, true); } else { - myControlFunction = CANNetworkManager::CANNetwork.get_internal_control_function(session->sessionMessage.get_destination_control_function()); - partnerControlFunction = session->sessionMessage.get_source_control_function(); - } - - data[0] = EXTENDED_CONNECTION_ABORT_MULTIPLEXOR; - data[1] = static_cast(reason); - data[2] = 0xFF; - data[3] = 0xFF; - data[4] = 0xFF; - data[5] = static_cast(pgn & 0xFF); - data[6] = static_cast((pgn >> 8) & 0xFF); - data[7] = static_cast((pgn >> 16) & 0xFF); - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - data.data(), - CAN_DATA_LENGTH, - myControlFunction, - partnerControlFunction, - CANIdentifier::CANPriority::PriorityLowest7); + // The session exists, but we're not in the right state to receive an EOM, by the standard we must ignore it + CANStackLogger::warn("[ETP]: Received an End Of Message Acknowledgement message for 0x%05X, but not expecting one, ignoring.", parameterGroupNumber); + } + } + else + { + CANStackLogger::warn("[ETP]: Received End Of Message Acknowledgement for 0x%05X while no session existed for this source and destination, ignoring.", parameterGroupNumber); } - return retVal; } - bool ExtendedTransportProtocolManager::abort_session(std::uint32_t parameterGroupNumber, ConnectionAbortReason reason, std::shared_ptr source, std::shared_ptr destination) + void ExtendedTransportProtocolManager::process_abort(const std::shared_ptr source, + const std::shared_ptr destination, + std::uint32_t parameterGroupNumber, + ExtendedTransportProtocolManager::ConnectionAbortReason reason) { - std::array data; + bool foundSession = false; - data[0] = EXTENDED_CONNECTION_ABORT_MULTIPLEXOR; - data[1] = static_cast(reason); - data[2] = 0xFF; - data[3] = 0xFF; - data[4] = 0xFF; - data[5] = static_cast(parameterGroupNumber & 0xFF); - data[6] = static_cast((parameterGroupNumber >> 8) & 0xFF); - data[7] = static_cast((parameterGroupNumber >> 16) & 0xFF); - return CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - data.data(), - CAN_DATA_LENGTH, - source, - destination, - CANIdentifier::CANPriority::PriorityLowest7); + auto session = get_session(source, destination); + if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) + { + foundSession = true; + CANStackLogger::error("[ETP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + close_session(*session, false); + } + session = get_session(destination, source); + if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) + { + foundSession = true; + CANStackLogger::error("[ETP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + close_session(*session, false); + } + + if (!foundSession) + { + CANStackLogger::warn("[ETP]: Received an abort (reason=%hu) with no matching session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); + } } - void ExtendedTransportProtocolManager::close_session(ExtendedTransportProtocolSession *session, bool successfull) + void ExtendedTransportProtocolManager::process_connection_management_message(const CANMessage &message) { - if (nullptr != session) + if (CAN_DATA_LENGTH != message.get_data_length()) + { + CANStackLogger::warn("[ETP]: Received a Connection Management message of invalid length %hu", message.get_data_length()); + return; + } + + const auto parameterGroupNumber = message.get_uint24_at(5); + + switch (message.get_uint8_at(0)) { - process_session_complete_callback(session, successfull); - auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); - if (activeSessions.end() != sessionLocation) + case REQUEST_TO_SEND_MULTIPLEXOR: + { + const auto totalMessageSize = message.get_uint32_at(1); + process_request_to_send(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + totalMessageSize); + } + break; + + case CLEAR_TO_SEND_MULTIPLEXOR: + { + const auto packetsToBeSent = message.get_uint8_at(1); + const auto nextPacketNumber = message.get_uint24_at(2); + process_clear_to_send(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + packetsToBeSent, + nextPacketNumber); + } + break; + + case DATA_PACKET_OFFSET_MULTIPLXOR: + { + const auto numberOfPackets = message.get_uint8_at(1); + const auto packetOffset = message.get_uint24_at(2); + process_data_packet_offset(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + numberOfPackets, + packetOffset); + } + break; + + case END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR: { - activeSessions.erase(sessionLocation); - delete session; - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Debug, "[ETP]: Session Closed"); + const auto numberOfBytesTransferred = message.get_uint32_at(1); + process_end_of_session_acknowledgement(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + numberOfBytesTransferred); } + break; + + case CONNECTION_ABORT_MULTIPLEXOR: + { + const auto reason = static_cast(message.get_uint8_at(1)); + process_abort(message.get_source_control_function(), + message.get_destination_control_function(), + parameterGroupNumber, + reason); + } + break; + + default: + { + CANStackLogger::warn("[ETP]: Bad Mux in Transport Protocol Connection Management message"); + } + break; } } - bool ExtendedTransportProtocolManager::get_session(ExtendedTransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination) const + void ExtendedTransportProtocolManager::process_data_transfer_message(const CANMessage &message) { - session = nullptr; + if (CAN_DATA_LENGTH != message.get_data_length()) + { + CANStackLogger::warn("[ETP]: Received a Data Transfer message of invalid length %hu", message.get_data_length()); + return; + } - for (auto i : activeSessions) + auto source = message.get_source_control_function(); + auto destination = message.get_destination_control_function(); + + auto sequenceNumber = message.get_uint8_at(SEQUENCE_NUMBER_DATA_INDEX); + + auto session = get_session(source, destination); + if (nullptr != session) { - if ((i->sessionMessage.get_source_control_function() == source) && - (i->sessionMessage.get_destination_control_function() == destination)) + if (StateMachineState::WaitForDataTransferPacket != session->state) { - session = i; - break; + CANStackLogger::warn("[ETP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); + abort_session(*session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); + } + else if (sequenceNumber == session->get_last_sequence_number()) + { + CANStackLogger::error("[ETP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); + abort_session(*session, ConnectionAbortReason::DuplicateSequenceNumber); + } + else if (sequenceNumber == (session->get_last_sequence_number() + 1)) + { + // Convert data type to a vector to allow for manipulation + auto &data = static_cast(session->get_data()); + + // Correct sequence number, copy the data + for (std::uint8_t i = 0; i < PROTOCOL_BYTES_PER_FRAME; i++) + { + std::uint32_t currentDataIndex = (PROTOCOL_BYTES_PER_FRAME * session->get_last_packet_number()) + i; + if (currentDataIndex < session->get_message_length()) + { + data.set_byte(currentDataIndex, message.get_uint8_at(1 + i)); + } + else + { + // Reached the end of the message, no need to copy any more data + break; + } + } + + session->set_last_sequency_number(sequenceNumber); + if (session->get_number_of_remaining_packets() == 0) + { + // Send End of Message Acknowledgement for sessions with specific destination only + send_end_of_session_acknowledgement(*session); + + // Construct the completed message + CANMessage completedMessage(0); + completedMessage.set_identifier(CANIdentifier(CANIdentifier::Type::Extended, + session->get_parameter_group_number(), + CANIdentifier::CANPriority::PriorityDefault6, + destination->get_address(), + source->get_address())); + completedMessage.set_source_control_function(source); + completedMessage.set_destination_control_function(destination); + completedMessage.set_data(data.data().begin(), static_cast(data.size())); + + canMessageReceivedCallback(completedMessage); + close_session(*session, true); + } + else if (session->get_dpo_number_of_packets_remaining() == 0) + { + send_clear_to_send(*session); + } + } + else + { + CANStackLogger::error("[ETP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); + abort_session(*session, ConnectionAbortReason::BadSequenceNumber); } } - return (nullptr != session); } - bool ExtendedTransportProtocolManager::get_session(ExtendedTransportProtocolSession *&session, std::shared_ptr source, std::shared_ptr destination, std::uint32_t parameterGroupNumber) const + void ExtendedTransportProtocolManager::process_message(const CANMessage &message) { - bool retVal = false; - session = nullptr; - - if ((get_session(session, source, destination)) && - (session->sessionMessage.get_identifier().get_parameter_group_number() == parameterGroupNumber)) + // TODO: Allow sniffing of messages to all addresses, not just the ones we normally listen to (#297) + if (message.has_valid_source_control_function() && message.has_valid_destination_control_function()) { - retVal = true; + switch (message.get_identifier().get_parameter_group_number()) + { + case static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement): + { + process_connection_management_message(message); + } + break; + + case static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolDataTransfer): + { + process_data_transfer_message(message); + } + break; + + default: + break; + } } - return retVal; } - void ExtendedTransportProtocolManager::process_session_complete_callback(ExtendedTransportProtocolSession *session, bool success) + bool ExtendedTransportProtocolManager::protocol_transmit_message(std::uint32_t parameterGroupNumber, + std::unique_ptr &data, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) { - if ((nullptr != session) && - (nullptr != session->sessionCompleteCallback) && - (nullptr != session->sessionMessage.get_source_control_function()) && - (ControlFunction::Type::Internal == session->sessionMessage.get_source_control_function()->get_type())) + // Return false early if we can't send the message + if ((nullptr == data) || (data->size() <= CAN_DATA_LENGTH) || (data->size() > MAX_PROTOCOL_DATA_LENGTH)) + { + // Invalid message length + return false; + } + else if ((nullptr == source) || (!source->get_address_valid()) || has_session(source, destination)) { - session->sessionCompleteCallback(session->sessionMessage.get_identifier().get_parameter_group_number(), - session->get_message_data_length(), - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - success, - session->parent); + return false; } + + // We can handle this message! If we only have a view of the data, let's clone the data, + // so we don't have to worry about it being deleted. + data = data->copy_if_not_owned(std::move(data)); + + ExtendedTransportProtocolSession session = ExtendedTransportProtocolSession::create_transmit_session(parameterGroupNumber, + std::move(data), + source, + destination, + configuration->get_number_of_packets_per_cts_message(), + sessionCompleteCallback, + parentPointer); + + session.set_state(StateMachineState::SendRequestToSend); + CANStackLogger::debug("[ETP]: New tx session for 0x%05X. Source: %hu, destination: %hu", + parameterGroupNumber, + source->get_address(), + destination->get_address()); + + activeSessions.push_back(std::move(session)); + return true; } - bool ExtendedTransportProtocolManager::send_end_of_session_acknowledgement(ExtendedTransportProtocolSession *session) const + void ExtendedTransportProtocolManager::update() { - bool retVal = false; - - if (nullptr != session) + // We use a fancy for loop here to allow us to remove sessions from the list while iterating + for (std::size_t i = activeSessions.size(); i > 0; i--) { - std::uint32_t totalBytesTransferred = (session->get_message_data_length()); - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { EXTENDED_END_OF_MESSAGE_ACKNOWLEDGEMENT, - static_cast(totalBytesTransferred & 0xFF), - static_cast((totalBytesTransferred >> 8) & 0xFF), - static_cast((totalBytesTransferred >> 16) & 0xFF), - static_cast((totalBytesTransferred >> 24) & 0xFF), - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_destination_control_function()), - session->sessionMessage.get_source_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); + auto &session = activeSessions.at(i - 1); + if (!session.get_source()->get_address_valid()) + { + CANStackLogger::warn("[ETP]: Closing active session as the source control function is no longer valid"); + close_session(session, false); + } + else if (!session.get_destination()->get_address_valid()) + { + CANStackLogger::warn("[ETP]: Closing active session as the destination control function is no longer valid"); + close_session(session, false); + } + else if (StateMachineState::None != session.state) + { + update_state_machine(session); + } } - return retVal; } - bool ExtendedTransportProtocolManager::send_extended_connection_mode_clear_to_send(ExtendedTransportProtocolSession *session) const + void ExtendedTransportProtocolManager::send_data_transfer_packets(ExtendedTransportProtocolSession &session) const { - bool retVal = false; + std::array buffer; + std::uint8_t framesToSend = session.get_dpo_number_of_packets_remaining(); + if (framesToSend > configuration->get_max_number_of_network_manager_protocol_frames_per_update()) + { + framesToSend = configuration->get_max_number_of_network_manager_protocol_frames_per_update(); + } - if (nullptr != session) + // Try and send packets + for (std::uint8_t i = 0; i < framesToSend; i++) { - std::uint32_t packetMax = ((((session->get_message_data_length() - 1) / 7) + 1) - session->processedPacketsThisSession); + buffer[0] = session.get_last_sequence_number() + 1; - if (packetMax > 0xFF) + std::uint32_t dataOffset = session.get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) { - packetMax = 0xFF; + std::uint32_t index = dataOffset + j; + if (index < session.get_message_length()) + { + buffer[1 + j] = session.get_data().get_byte(index); + } + else + { + buffer[1 + j] = 0xFF; + } } - if (packetMax < session->packetCount) + if (sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolDataTransfer), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_source()), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7)) { - session->packetCount = packetMax; // If we're sending a CTS with less than 0xFF, set the expected packet count to the CTS packet count + session.set_last_sequency_number(session.get_last_sequence_number() + 1); + } + else + { + // Process more next time protocol is updated + break; } - - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { EXTENDED_CLEAR_TO_SEND_MULTIPLEXOR, - static_cast(packetMax), /// @todo Make CTS Max user configurable - static_cast((session->processedPacketsThisSession + 1) & 0xFF), - static_cast(((session->processedPacketsThisSession + 1) >> 8) & 0xFF), - static_cast(((session->processedPacketsThisSession + 1) >> 16) & 0xFF), - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_destination_control_function()), - session->sessionMessage.get_source_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); } - return retVal; - } - - bool ExtendedTransportProtocolManager::send_extended_connection_mode_request_to_send(const ExtendedTransportProtocolSession *session) const - { - bool retVal = false; - if (nullptr != session) + if (session.get_number_of_remaining_packets() == 0) { - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { EXTENDED_REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session->get_message_data_length() & 0xFF), - static_cast((session->get_message_data_length() >> 8) & 0xFF), - static_cast((session->get_message_data_length() >> 16) & 0xFF), - static_cast((session->get_message_data_length() >> 24) & 0xFF), - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); + session.set_state(StateMachineState::WaitForEndOfMessageAcknowledge); } - return retVal; - } - - bool ExtendedTransportProtocolManager::send_extended_connection_mode_data_packet_offset(const ExtendedTransportProtocolSession *session) const - { - bool retVal = false; - - if (nullptr != session) + else if (session.get_dpo_number_of_packets_remaining() == 0) { - const std::uint8_t dataBuffer[CAN_DATA_LENGTH] = { EXTENDED_DATA_PACKET_OFFSET_MULTIPLEXOR, - static_cast(session->packetCount & 0xFF), - static_cast((session->processedPacketsThisSession) & 0xFF), - static_cast((session->processedPacketsThisSession >> 8) & 0xFF), - static_cast((session->processedPacketsThisSession >> 16) & 0xFF), - static_cast(session->sessionMessage.get_identifier().get_parameter_group_number() & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 8) & 0xFF), - static_cast((session->sessionMessage.get_identifier().get_parameter_group_number() >> 16) & 0xFF) }; - retVal = CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - CANIdentifier::CANPriority::PriorityDefault6); + session.set_state(StateMachineState::WaitForClearToSend); } - return retVal; } - void ExtendedTransportProtocolManager::set_state(ExtendedTransportProtocolSession *session, StateMachineState value) + void ExtendedTransportProtocolManager::update_state_machine(ExtendedTransportProtocolSession &session) { - if (nullptr != session) + switch (session.state) { - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - session->state = value; - } - } + case StateMachineState::None: + break; - void ExtendedTransportProtocolManager::update_state_machine(ExtendedTransportProtocolSession *session) - { - if (nullptr != session) - { - switch (session->state) + case StateMachineState::SendRequestToSend: { - case StateMachineState::RequestToSend: + if (send_request_to_send(session)) { - if (send_extended_connection_mode_request_to_send(session)) + session.set_state(StateMachineState::WaitForClearToSend); + } + } + break; + + case StateMachineState::WaitForClearToSend: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + { + CANStackLogger::error("[TP]: Timeout rx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); + if (session.get_dpo_number_of_packets() > 0) { - set_state(session, StateMachineState::WaitForClearToSend); + // A connection is only considered established if we've received at least one CTS before + // And we can only abort a connection if it's considered established + abort_session(session, ConnectionAbortReason::Timeout); } else { - if (SystemTiming::time_expired_ms(session->timestamp_ms, T2_3_TIMEOUT_MS)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Aborting session, T2-3 timeout reached while in RTS state"); - abort_session(session, ConnectionAbortReason::Timeout); - close_session(session, false); - } + close_session(session, false); } } - break; + } + break; - case StateMachineState::WaitForEndOfMessageAcknowledge: - case StateMachineState::WaitForExtendedDataPacketOffset: - case StateMachineState::WaitForClearToSend: + case StateMachineState::SendClearToSend: + { + if (send_clear_to_send(session)) { - if (SystemTiming::time_expired_ms(session->timestamp_ms, T2_3_TIMEOUT_MS)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Aborting session, T2-3 timeout reached while waiting for CTS"); - abort_session(session, ConnectionAbortReason::Timeout); - close_session(session, false); - } + session.set_state(StateMachineState::WaitForDataPacketOffset); } - break; + } + break; - case StateMachineState::TxDataSession: + case StateMachineState::WaitForDataPacketOffset: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) { - if (nullptr != session->sessionMessage.get_destination_control_function()) - { - std::uint8_t dataBuffer[CAN_DATA_LENGTH]; - bool proceedToSendDataPackets = true; - bool sessionStillValid = true; - - if (0 == session->lastPacketNumber) - { - proceedToSendDataPackets = send_extended_connection_mode_data_packet_offset(session); - } - - if (proceedToSendDataPackets) - { - std::uint32_t framesSentThisUpdate = 0; - // Try and send packets - for (std::uint32_t i = session->lastPacketNumber; i < session->packetCount; i++) - { - dataBuffer[0] = (session->lastPacketNumber + 1); - - if (nullptr != session->frameChunkCallback) - { - // Use the callback to get this frame's data - std::uint8_t callbackBuffer[7] = { - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF, - 0xFF - }; - std::uint32_t numberBytesLeft = (session->get_message_data_length() - (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession)); - - if (numberBytesLeft > PROTOCOL_BYTES_PER_FRAME) - { - numberBytesLeft = PROTOCOL_BYTES_PER_FRAME; - } - - bool callbackSuccessful = session->frameChunkCallback(dataBuffer[0], (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession), numberBytesLeft, callbackBuffer, session->parent); - - if (callbackSuccessful) - { - for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) - { - dataBuffer[1 + j] = callbackBuffer[j]; - } - } - else - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Aborting session, unable to transfer chunk of data (numberBytesLeft=" + to_string(numberBytesLeft) + ")"); - abort_session(session, ConnectionAbortReason::AnyOtherReason); - close_session(session, false); - sessionStillValid = false; - break; - } - } - else - { - // Use the data buffer to get the data for this frame - for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) - { - std::uint32_t index = (j + (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession)); - if (index < session->get_message_data_length()) - { - dataBuffer[1 + j] = session->sessionMessage.get_data()[j + (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession)]; - } - else - { - dataBuffer[1 + j] = 0xFF; - } - } - } - - if (CANNetworkManager::CANNetwork.send_can_message(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolDataTransfer), - dataBuffer, - CAN_DATA_LENGTH, - std::static_pointer_cast(session->sessionMessage.get_source_control_function()), - session->sessionMessage.get_destination_control_function(), - CANIdentifier::CANPriority::PriorityLowest7)) - { - framesSentThisUpdate++; - session->lastPacketNumber++; - session->processedPacketsThisSession++; - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - - if (framesSentThisUpdate >= CANNetworkManager::CANNetwork.get_configuration().get_max_number_of_network_manager_protocol_frames_per_update()) - { - break; // Throttle the session - } - } - else - { - // Process more next time protocol is updated - break; - } - } - } - - if (sessionStillValid) - { - if ((session->lastPacketNumber == (session->packetCount)) && - (session->get_message_data_length() <= (PROTOCOL_BYTES_PER_FRAME * session->processedPacketsThisSession))) - { - set_state(session, StateMachineState::WaitForEndOfMessageAcknowledge); - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - } - else if (session->lastPacketNumber == session->packetCount) - { - set_state(session, StateMachineState::WaitForClearToSend); - session->timestamp_ms = SystemTiming::get_timestamp_ms(); - } - } - } + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected DPO)", session.get_parameter_group_number()); + abort_session(session, ConnectionAbortReason::Timeout); } - break; + } + break; - case StateMachineState::RxDataSession: + case StateMachineState::SendDataPacketOffset: + { + if (send_data_packet_offset(session)) { - if (session->packetCount == session->lastPacketNumber) - { - set_state(session, StateMachineState::ClearToSend); - } - else if (SystemTiming::time_expired_ms(session->timestamp_ms, T1_TIMEOUT_MS)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Aborting session, RX T1 timeout reached"); - abort_session(session, ConnectionAbortReason::Timeout); - close_session(session, false); - } + session.set_state(StateMachineState::SendDataTransferPackets); } - break; + } + break; - case StateMachineState::ClearToSend: + case StateMachineState::SendDataTransferPackets: + { + send_data_transfer_packets(session); + } + break; + + case StateMachineState::WaitForDataTransferPacket: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T1_TIMEOUT_MS)) { - if (send_extended_connection_mode_clear_to_send(session)) - { - set_state(session, StateMachineState::WaitForExtendedDataPacketOffset); - } - else if (SystemTiming::time_expired_ms(session->timestamp_ms, T2_3_TIMEOUT_MS)) - { - CANStackLogger::CAN_stack_log(CANStackLogger::LoggingLevel::Error, "[ETP]: Aborting session, T2-3 timeout reached while in CTS state"); - abort_session(session, ConnectionAbortReason::Timeout); - close_session(session, false); - } + CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); + abort_session(session, ConnectionAbortReason::Timeout); } - break; + } + break; - default: + case StateMachineState::WaitForEndOfMessageAcknowledge: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) { - // Nothing to do. + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); + abort_session(session, ConnectionAbortReason::Timeout); } - break; + } + break; + } + } + + bool ExtendedTransportProtocolManager::abort_session(const ExtendedTransportProtocolSession &session, ConnectionAbortReason reason) + { + bool retVal = false; + std::shared_ptr myControlFunction; + std::shared_ptr partnerControlFunction; + if (ExtendedTransportProtocolSession::Direction::Transmit == session.get_direction()) + { + myControlFunction = std::static_pointer_cast(session.get_source()); + partnerControlFunction = session.get_destination(); + } + else + { + myControlFunction = std::static_pointer_cast(session.get_destination()); + partnerControlFunction = session.get_source(); + } + + if ((nullptr != myControlFunction) && (nullptr != partnerControlFunction)) + { + retVal = send_abort(myControlFunction, partnerControlFunction, session.get_parameter_group_number(), reason); + } + close_session(session, false); + return retVal; + } + + bool ExtendedTransportProtocolManager::send_abort(std::shared_ptr sender, + std::shared_ptr receiver, + std::uint32_t parameterGroupNumber, + ConnectionAbortReason reason) const + { + const std::array buffer{ + CONNECTION_ABORT_MULTIPLEXOR, + static_cast(reason), + 0xFF, + 0xFF, + 0xFF, + static_cast(parameterGroupNumber & 0xFF), + static_cast((parameterGroupNumber >> 8) & 0xFF), + static_cast((parameterGroupNumber >> 16) & 0xFF) + }; + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + sender, + receiver, + CANIdentifier::CANPriority::PriorityLowest7); + } + + void ExtendedTransportProtocolManager::close_session(const ExtendedTransportProtocolSession &session, bool successful) + { + if ((nullptr != session.sessionCompleteCallback) && (ExtendedTransportProtocolSession::Direction::Transmit == session.get_direction())) + { + if (auto source = session.get_source()) + { + session.sessionCompleteCallback(session.get_parameter_group_number(), + session.get_message_length(), + std::static_pointer_cast(source), + session.get_destination(), + successful, + session.parent); + } + } + + auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); + if (activeSessions.end() != sessionLocation) + { + activeSessions.erase(sessionLocation); + CANStackLogger::debug("[ETP]: Session Closed"); + } + } + + bool ExtendedTransportProtocolManager::send_request_to_send(const ExtendedTransportProtocolSession &session) const + { + bool retVal = false; + if (auto source = session.get_source()) + { + const std::array buffer{ + REQUEST_TO_SEND_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + static_cast((session.get_message_length() >> 16) & 0xFF), + static_cast((session.get_message_length() >> 24) & 0xFF), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(source), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7); + } + return retVal; + } + + bool ExtendedTransportProtocolManager::send_clear_to_send(ExtendedTransportProtocolSession &session) const + { + bool retVal = false; + // Since we're the receiving side, we are the destination of the session + if (auto ourControlFunction = session.get_destination()) + { + std::uint32_t nextPacketNumber = session.get_last_packet_number() + 1; + const std::array buffer{ + CLEAR_TO_SEND_MULTIPLEXOR, + session.get_cts_number_of_packet_limit(), + static_cast(nextPacketNumber & 0xFF), + static_cast((nextPacketNumber >> 8) & 0xFF), + static_cast((nextPacketNumber >> 16) & 0xFF), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(ourControlFunction), + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + if (retVal) + { + session.set_acknowledged_packet_number(session.get_last_packet_number()); } } + return retVal; } -} // namespace isobus + bool isobus::ExtendedTransportProtocolManager::send_data_packet_offset(ExtendedTransportProtocolSession &session) const + { + bool retVal = false; + if (auto source = session.get_source()) + { + std::uint8_t packetsThisSegment = session.get_number_of_remaining_packets(); + if (packetsThisSegment > session.get_cts_number_of_packet_limit()) + { + packetsThisSegment = session.get_cts_number_of_packet_limit(); + } + else if (packetsThisSegment > 16) + { + //! @todo apply CTS number of packets recommendation of 16 via a configuration option + packetsThisSegment = 16; + } + + const std::array buffer{ + DATA_PACKET_OFFSET_MULTIPLXOR, + packetsThisSegment, + static_cast(session.get_last_packet_number()), + static_cast((session.get_last_packet_number() >> 8) & 0xFF), + static_cast((session.get_last_packet_number() >> 16) & 0xFF), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(source), + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + if (retVal) + { + session.set_dpo_number_of_packets(packetsThisSegment); + } + } + return retVal; + } + + bool ExtendedTransportProtocolManager::send_end_of_session_acknowledgement(const ExtendedTransportProtocolSession &session) const + { + bool retVal = false; + // Since we're the receiving side, we are the destination of the session + if (auto ourControlFunction = session.get_destination()) + { + std::uint32_t messageLength = session.get_message_length(); + std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); + + const std::array buffer{ + END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, + static_cast(messageLength & 0xFF), + static_cast((messageLength >> 8) & 0xFF), + static_cast((messageLength >> 16) & 0xFF), + static_cast((messageLength >> 24) & 0xFF), + static_cast(parameterGroupNumber & 0xFF), + static_cast((parameterGroupNumber >> 8) & 0xFF), + static_cast((parameterGroupNumber >> 16) & 0xFF), + }; + + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(ourControlFunction), + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + } + else + { + CANStackLogger::warn("[ETP]: Attempted to send EOM to null session"); + } + return retVal; + } + + bool ExtendedTransportProtocolManager::has_session(std::shared_ptr source, std::shared_ptr destination) + { + return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const ExtendedTransportProtocolSession &session) { + return session.matches(source, destination); + }); + } + + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession *ExtendedTransportProtocolManager::get_session(std::shared_ptr source, + std::shared_ptr destination) + { + auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const ExtendedTransportProtocolSession &session) { + return session.matches(source, destination); + }); + // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored + return (activeSessions.end() != result) ? &(*result) : nullptr; + } +} diff --git a/isobus/src/can_network_manager.cpp b/isobus/src/can_network_manager.cpp index f22cbfc2..0108901d 100644 --- a/isobus/src/can_network_manager.cpp +++ b/isobus/src/can_network_manager.cpp @@ -33,7 +33,6 @@ namespace isobus { receiveMessageList.clear(); initialized = true; - extendedTransportProtocol.initialize({}); } std::shared_ptr CANNetworkManager::get_control_function(std::uint8_t channelIndex, std::uint8_t address, CANLibBadge) const @@ -150,6 +149,16 @@ namespace isobus // Successfully sent via the transport protocol retVal = true; } + else if (extendedTransportProtocol.protocol_transmit_message(parameterGroupNumber, + messageData, + sourceControlFunction, + destinationControlFunction, + transmitCompleteCallback, + parentPointer)) + { + // Successfully sent via the extended transport protocol + retVal = true; + } else { //! @todo convert the other protocols to stop using the abstract protocollib class @@ -490,7 +499,14 @@ namespace isobus std::shared_ptr destinationControlFunction, CANIdentifier::CANPriority priority) { return this->send_can_message(parameterGroupNumber, data.begin(), data.size(), sourceControlFunction, destinationControlFunction, priority); }, [this](const CANMessage &message) { this->protocol_message_callback(message); }, - &configuration) + &configuration), + extendedTransportProtocol([this](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { return this->send_can_message(parameterGroupNumber, data.begin(), data.size(), sourceControlFunction, destinationControlFunction, priority); }, + [this](const CANMessage &message) { this->protocol_message_callback(message); }, + &configuration) { currentBusloadBitAccumulator.fill(0); lastAddressClaimRequestTimestamp_ms.fill(0); diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index b2c62a8c..3b2488f0 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -201,8 +201,8 @@ namespace isobus return totalNumberOfPackets; } - TransportProtocolManager::TransportProtocolManager(const SendCANFrameCallback &sendCANFrameCallback, - const CANMessageReceivedCallback &canMessageReceivedCallback, + TransportProtocolManager::TransportProtocolManager(const CANMessageFrameCallback &sendCANFrameCallback, + const CANMessageCallback &canMessageReceivedCallback, const CANNetworkConfiguration *configuration) : sendCANFrameCallback(sendCANFrameCallback), canMessageReceivedCallback(canMessageReceivedCallback), @@ -238,7 +238,7 @@ namespace isobus 0xFF, // Arbitrary - unused for broadcast source, nullptr); // Global destination - newSession.set_state(StateMachineState::RxDataSession); + newSession.set_state(StateMachineState::WaitForDataTransferPacket); activeSessions.push_back(std::move(newSession)); CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); @@ -289,7 +289,7 @@ namespace isobus clearToSendPacketMax, source, destination); - newSession.set_state(StateMachineState::ClearToSend); + newSession.set_state(StateMachineState::SendClearToSend); activeSessions.push_back(std::move(newSession)); } } @@ -329,7 +329,7 @@ namespace isobus // Just sit here in this state until we get a non-zero packet count if (0 != packetsToBeSent) { - session->set_state(StateMachineState::TxDataSession); + session->set_state(StateMachineState::SendDataTransferPackets); } } } @@ -349,7 +349,7 @@ namespace isobus { if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) { - CANStackLogger::debug("[TP]: Completed rx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); + CANStackLogger::debug("[TP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); session->state = StateMachineState::None; close_session(*session, true); } @@ -519,7 +519,7 @@ namespace isobus auto session = get_session(source, destination); if (nullptr != session) { - if (StateMachineState::RxDataSession != session->state) + if (StateMachineState::WaitForDataTransferPacket != session->state) { CANStackLogger::warn("[TP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); abort_session(*session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); @@ -651,7 +651,7 @@ namespace isobus if (session.is_broadcast()) { // Broadcast message - session.set_state(StateMachineState::BroadcastAnnounce); + session.set_state(StateMachineState::SendBroadcastAnnounce); CANStackLogger::debug("[TP]: New broadcast tx session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); @@ -659,7 +659,7 @@ namespace isobus else { // Destination specific message - session.set_state(StateMachineState::RequestToSend); + session.set_state(StateMachineState::SendRequestToSend); CANStackLogger::debug("[TP]: New tx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), @@ -695,7 +695,7 @@ namespace isobus void TransportProtocolManager::send_data_transfer_packets(TransportProtocolSession &session) { std::array buffer; - std::uint32_t framesToSend = session.get_cts_number_of_packets_remaining(); + std::uint8_t framesToSend = session.get_cts_number_of_packets_remaining(); if (session.is_broadcast()) { framesToSend = 1; @@ -710,10 +710,10 @@ namespace isobus { buffer[0] = session.get_last_sequence_number() + 1; - std::uint8_t dataOffset = session.get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + std::uint16_t dataOffset = session.get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) { - std::uint32_t index = dataOffset + j; + std::uint16_t index = dataOffset + j; if (index < session.get_message_length()) { buffer[1 + j] = session.get_data().get_byte(index); @@ -764,11 +764,20 @@ namespace isobus case StateMachineState::None: break; - case StateMachineState::ClearToSend: + case StateMachineState::SendBroadcastAnnounce: { - if (send_clear_to_send(session)) + if (send_broadcast_announce_message(session)) { - session.set_state(StateMachineState::RxDataSession); + session.set_state(StateMachineState::SendDataTransferPackets); + } + } + break; + + case StateMachineState::SendRequestToSend: + { + if (send_request_to_send(session)) + { + session.set_state(StateMachineState::WaitForClearToSend); } } break; @@ -791,35 +800,17 @@ namespace isobus } } break; - case StateMachineState::WaitForEndOfMessageAcknowledge: - { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) - { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); - abort_session(session, ConnectionAbortReason::Timeout); - } - } - break; - - case StateMachineState::RequestToSend: - { - if (send_request_to_send(session)) - { - session.set_state(StateMachineState::WaitForClearToSend); - } - } - break; - case StateMachineState::BroadcastAnnounce: + case StateMachineState::SendClearToSend: { - if (send_broadcast_announce_message(session)) + if (send_clear_to_send(session)) { - session.set_state(StateMachineState::TxDataSession); + session.set_state(StateMachineState::WaitForDataTransferPacket); } } break; - case StateMachineState::TxDataSession: + case StateMachineState::SendDataTransferPackets: { if (session.is_broadcast() && (!SystemTiming::time_expired_ms(session.timestamp_ms, configuration->get_minimum_time_between_transport_protocol_bam_frames()))) { @@ -832,7 +823,7 @@ namespace isobus } break; - case StateMachineState::RxDataSession: + case StateMachineState::WaitForDataTransferPacket: { if (session.is_broadcast()) { @@ -863,6 +854,16 @@ namespace isobus } } break; + + case StateMachineState::WaitForEndOfMessageAcknowledge: + { + if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + { + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); + abort_session(session, ConnectionAbortReason::Timeout); + } + } + break; } } @@ -959,6 +960,30 @@ namespace isobus return retVal; } + bool TransportProtocolManager::send_request_to_send(const TransportProtocolSession &session) const + { + bool retVal = false; + if (auto source = session.get_source()) + { + const std::array buffer{ + REQUEST_TO_SEND_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + session.get_total_number_of_packets(), + session.get_rts_number_of_packet_limit(), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(source), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7); + } + return retVal; + } + bool TransportProtocolManager::send_clear_to_send(TransportProtocolSession &session) const { bool retVal = false; @@ -1000,30 +1025,6 @@ namespace isobus return retVal; } - bool TransportProtocolManager::send_request_to_send(const TransportProtocolSession &session) const - { - bool retVal = false; - if (auto source = session.get_source()) - { - const std::array buffer{ - REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - session.get_total_number_of_packets(), - session.get_rts_number_of_packet_limit(), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(source), - session.get_destination(), - CANIdentifier::CANPriority::PriorityLowest7); - } - return retVal; - } - bool TransportProtocolManager::send_end_of_session_acknowledgement(const TransportProtocolSession &session) const { bool retVal = false; From fb2709e1fc3f83404854df97de88d1ee1057ee1c Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Wed, 20 Dec 2023 14:49:16 +0100 Subject: [PATCH 3/9] feat(tp/etp): base class for sessions --- isobus/CMakeLists.txt | 2 + .../can_extended_transport_protocol.hpp | 139 ++---- .../isobus/isobus/can_transport_protocol.hpp | 146 ++---- .../isobus/can_transport_protocol_base.hpp | 137 ++++++ .../src/can_extended_transport_protocol.cpp | 220 +++------ isobus/src/can_transport_protocol.cpp | 421 ++++++++---------- isobus/src/can_transport_protocol_base.cpp | 98 ++++ 7 files changed, 549 insertions(+), 614 deletions(-) create mode 100644 isobus/include/isobus/isobus/can_transport_protocol_base.hpp create mode 100644 isobus/src/can_transport_protocol_base.cpp diff --git a/isobus/CMakeLists.txt b/isobus/CMakeLists.txt index 4ab484d5..886178ab 100644 --- a/isobus/CMakeLists.txt +++ b/isobus/CMakeLists.txt @@ -20,6 +20,7 @@ set(ISOBUS_SRC "can_partnered_control_function.cpp" "can_NAME_filter.cpp" "can_transport_protocol.cpp" + "can_transport_protocol_base.cpp" "can_stack_logger.cpp" "can_network_configuration.cpp" "can_callbacks.cpp" @@ -59,6 +60,7 @@ set(ISOBUS_INCLUDE "can_address_claim_state_machine.hpp" "can_NAME_filter.hpp" "can_transport_protocol.hpp" + "can_transport_protocol_base.hpp" "can_stack_logger.hpp" "can_network_configuration.hpp" "can_callbacks.hpp" diff --git a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp index a2d18ede..8ba8fd9c 100644 --- a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp @@ -6,17 +6,15 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_EXTENDED_TRANSPORT_PROTOCOL_HPP #define CAN_EXTENDED_TRANSPORT_PROTOCOL_HPP -#include "isobus/isobus/can_control_function.hpp" -#include "isobus/isobus/can_message.hpp" -#include "isobus/isobus/can_message_data.hpp" #include "isobus/isobus/can_message_frame.hpp" #include "isobus/isobus/can_network_configuration.hpp" +#include "isobus/isobus/can_transport_protocol_base.hpp" namespace isobus { @@ -71,89 +69,38 @@ namespace isobus /// /// @brief A storage object to keep track of session information internally //================================================================================================ - class ExtendedTransportProtocolSession + class ExtendedTransportProtocolSession : public TransportProtocolSessionBase { public: - /// @brief Enumerates the possible session directions, Rx or Tx - enum class Direction - { - Transmit, ///< We are transmitting a message - Receive ///< We are receiving a message - }; - - /// @brief A useful way to compare session objects to each other for equality - /// @param[in] obj The object to compare to - /// @returns true if the objects are equal, false if not - bool operator==(const ExtendedTransportProtocolSession &obj) const; - - /// @brief Checks if the source and destination control functions match the given control functions. - /// @param[in] other_source The control function to compare with the source control function. - /// @param[in] other_destination The control function to compare with the destination control function. - /// @returns True if the source and destination control functions match the given control functions, false otherwise. - bool matches(std::shared_ptr other_source, std::shared_ptr other_destination) const; - - /// @brief Get the direction of the session - /// @return The direction of the session - Direction get_direction() const; + ~ExtendedTransportProtocolSession() override = default; ///< Default destructor + ExtendedTransportProtocolSession(const ExtendedTransportProtocolSession &obj) = delete; ///< No copy constructor + ExtendedTransportProtocolSession &operator=(const ExtendedTransportProtocolSession &obj) = delete; ///< No copy assignment operator + + /// @brief The move constructor for a session + /// @param[in] obj The session to move + ExtendedTransportProtocolSession(ExtendedTransportProtocolSession &&obj) noexcept; + + /// @brief The move assignment operator for a session + /// @param[in] obj The session to move + /// @returns A reference to the moved session + ExtendedTransportProtocolSession &operator=(ExtendedTransportProtocolSession &&obj) noexcept; /// @brief Get the state of the session /// @return The state of the session StateMachineState get_state() const; - /// @brief Get the total number of bytes that will be sent or received in this session - /// @return The length of the message in number of bytes - std::uint32_t get_message_length() const; - - /// @brief Get the data buffer for the session - /// @return The data buffer for the session - CANMessageData &get_data() const; - - /// @brief Get the control function that is sending the message - /// @return The source control function - std::shared_ptr get_source() const; - - /// @brief Get the control function that is receiving the message - /// @return The destination control function - std::shared_ptr get_destination() const; + /// @brief Get the number of bytes that have been sent or received in this session + /// @return The number of bytes that have been sent or received + std::uint32_t get_total_bytes_transferred() const override; - /// @brief Get the parameter group number of the message - /// @return The PGN of the message - std::uint32_t get_parameter_group_number() const; + /// @brief Get the percentage of bytes that have been sent or received in this session + /// @return The percentage of bytes that have been sent or received + float get_percentage_bytes_transferred() const override; protected: friend class ExtendedTransportProtocolManager; ///< Allows the ETP manager full access - /// @brief Factory method to create a new receive session - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] totalMessageSize The total size of the message in bytes - /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session - /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the DPO message - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @returns A new receive session - static ExtendedTransportProtocolSession create_receive_session(std::uint32_t parameterGroupNumber, - std::uint32_t totalMessageSize, - std::uint32_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination); - - /// @brief Factory method to create a new transmit session - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] data Data buffer (will be moved into the session) - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the DPO message - /// @param[in] sessionCompleteCallback A callback for when the session completes - /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - /// @returns A new transmit session - static ExtendedTransportProtocolSession create_transmit_session(std::uint32_t parameterGroupNumber, - std::unique_ptr data, - std::shared_ptr source, - std::shared_ptr destination, - std::uint8_t clearToSendPacketMax, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer); + using TransportProtocolSessionBase::TransportProtocolSessionBase; ///< Inherit the base constructor /// @brief Set the state of the session /// @param[in] value The state to set the session to @@ -208,48 +155,13 @@ namespace isobus std::uint32_t get_total_number_of_packets() const; private: - /// @brief The constructor for a session - /// @param[in] direction The direction of the session - /// @param[in] data Data buffer (will be moved into the session) - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] totalMessageSize The total size of the message in bytes - /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session - /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] sessionCompleteCallback A callback for when the session completes - /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - ExtendedTransportProtocolSession(Direction direction, - std::unique_ptr data, - std::uint32_t parameterGroupNumber, - std::uint32_t totalMessageSize, - std::uint32_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer); - StateMachineState state = StateMachineState::None; ///< The state machine state for this session - Direction direction; ///< The direction of the session - std::uint32_t parameterGroupNumber; ///< The PGN of the message - std::unique_ptr data; ///< The data buffer for the message - std::uint32_t totalMessageSize; ///< The total size of the message in bytes - std::shared_ptr source; ///< The source control function - std::shared_ptr destination; ///< The destination control function - - std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts std::uint8_t lastSequenceNumber = 0; ///< The last processed sequence number for this set of packets std::uint32_t sequenceNumberOffset = 0; ///< The offset of the sequence number relative to the packet number std::uint32_t lastAcknowledgedPacketNumber = 0; ///< The last acknowledged packet number by the receiver - - std::uint32_t totalNumberOfPackets; ///< The total number of packets that will be sent or received in this session - std::uint8_t clearToSendPacketCount = 0; ///< The number of packets to be sent in response to one CTS - std::uint8_t clearToSendPacketCountMax = 0xFF; ///< The max packets that can be sent per CTS as indicated by the RTS message - - TransmitCompleteCallback sessionCompleteCallback = nullptr; ///< A callback that is to be called when the session is completed - void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr + std::uint8_t dataPacketOffsetPacketCount = 0; ///< The number of packets that will be sent with the current DPO + std::uint8_t clearToSendPacketCountLimit = 0xFF; ///< The max packets that can be sent per DPO as indicated by the CTS message }; static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 20; ///< ETP.CM_RTS Multiplexor @@ -354,8 +266,6 @@ namespace isobus /// @param[in] destination The shared pointer to the destination control function. /// @param[in] parameterGroupNumber The Parameter Group Number of the message. /// @param[in] totalMessageSize The total size of the message in bytes. - /// @param[in] totalNumberOfPackets The total number of packets to be sent. - /// @param[in] clearToSendPacketMax The maximum number of clear to send packets that can be sent. void process_request_to_send(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint32_t totalMessageSize); /// @brief Processes the Clear To Send (CTS) message. @@ -378,6 +288,7 @@ namespace isobus /// @param[in] source The source control function. /// @param[in] destination The destination control function. /// @param[in] parameterGroupNumber The parameter group number. + /// @param[in] numberOfBytesTransferred The number of bytes transferred. void process_end_of_session_acknowledgement(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, std::uint32_t numberOfBytesTransferred); /// @brief Processes an abort message in the CAN transport protocol. diff --git a/isobus/include/isobus/isobus/can_transport_protocol.hpp b/isobus/include/isobus/isobus/can_transport_protocol.hpp index 343cbef2..6b7c19fa 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol.hpp @@ -6,17 +6,15 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #ifndef CAN_TRANSPORT_PROTOCOL_HPP #define CAN_TRANSPORT_PROTOCOL_HPP -#include "isobus/isobus/can_control_function.hpp" -#include "isobus/isobus/can_message.hpp" -#include "isobus/isobus/can_message_data.hpp" #include "isobus/isobus/can_message_frame.hpp" #include "isobus/isobus/can_network_configuration.hpp" +#include "isobus/isobus/can_transport_protocol_base.hpp" namespace isobus { @@ -70,30 +68,21 @@ namespace isobus /// /// @brief A storage object to keep track of session information internally //================================================================================================ - class TransportProtocolSession + class TransportProtocolSession : public TransportProtocolSessionBase { public: - /// @brief Enumerates the possible session directions, Rx or Tx - enum class Direction - { - Transmit, ///< We are transmitting a message - Receive ///< We are receiving a message - }; - - /// @brief A useful way to compare session objects to each other for equality - /// @param[in] obj The object to compare to - /// @returns true if the objects are equal, false if not - bool operator==(const TransportProtocolSession &obj) const; - - /// @brief Checks if the source and destination control functions match the given control functions. - /// @param[in] other_source The control function to compare with the source control function. - /// @param[in] other_destination The control function to compare with the destination control function. - /// @returns True if the source and destination control functions match the given control functions, false otherwise. - bool matches(std::shared_ptr other_source, std::shared_ptr other_destination) const; - - /// @brief Get the direction of the session - /// @return The direction of the session - Direction get_direction() const; + ~TransportProtocolSession() override = default; ///< Default destructor + TransportProtocolSession(const TransportProtocolSession &obj) = delete; ///< No copy constructor + TransportProtocolSession &operator=(const TransportProtocolSession &obj) = delete; ///< No copy assignment operator + + /// @brief The move constructor for a session + /// @param[in] obj The session to move + TransportProtocolSession(TransportProtocolSession &&obj) noexcept; + + /// @brief The move assignment operator for a session + /// @param[in] obj The session to move + /// @returns A reference to the moved session + TransportProtocolSession &operator=(TransportProtocolSession &&obj) noexcept; /// @brief Get the state of the session /// @return The state of the session @@ -101,62 +90,42 @@ namespace isobus /// @brief Get the total number of bytes that will be sent or received in this session /// @return The length of the message in number of bytes - std::uint32_t get_message_length() const; - - /// @brief Get the data buffer for the session - /// @return The data buffer for the session - CANMessageData &get_data() const; - - /// @brief Get the control function that is sending the message - /// @return The source control function - std::shared_ptr get_source() const; - - /// @brief Get the control function that is receiving the message - /// @return The destination control function - std::shared_ptr get_destination() const; - - /// @brief Get the parameter group number of the message - /// @return The PGN of the message - std::uint32_t get_parameter_group_number() const; + std::uint16_t get_message_length() const; /// @brief Get whether or not this session is a broadcast session (BAM) /// @return True if this session is a broadcast session, false if not bool is_broadcast() const; + /// @brief Get the number of bytes that have been sent or received in this session + /// @return The number of bytes that have been sent or received + std::uint32_t get_total_bytes_transferred() const override; + + /// @brief Get the percentage of bytes that have been sent or received in this session + /// @return The percentage of bytes that have been sent or received + float get_percentage_bytes_transferred() const override; + protected: friend class TransportProtocolManager; ///< Allows the TP manager full access - /// @brief Factory method to create a new receive session + /// @brief The constructor for a session + /// @param[in] direction The direction of the session + /// @param[in] data Data buffer (will be moved into the session) /// @param[in] parameterGroupNumber The PGN of the message /// @param[in] totalMessageSize The total size of the message in bytes - /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message /// @param[in] source The source control function /// @param[in] destination The destination control function - /// @returns A new receive session - static TransportProtocolSession create_receive_session(std::uint32_t parameterGroupNumber, - std::uint16_t totalMessageSize, - std::uint8_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination); - - /// @brief Factory method to create a new transmit session - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] data Data buffer (will be moved into the session) - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message /// @param[in] sessionCompleteCallback A callback for when the session completes /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - /// @returns A new transmit session - static TransportProtocolSession create_transmit_session(std::uint32_t parameterGroupNumber, - std::unique_ptr data, - std::shared_ptr source, - std::shared_ptr destination, - std::uint8_t clearToSendPacketMax, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer); + TransportProtocolSession(TransportProtocolSessionBase::Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); /// @brief Set the state of the session /// @param[in] value The state to set the session to @@ -203,47 +172,12 @@ namespace isobus std::uint8_t get_total_number_of_packets() const; private: - /// @brief The constructor for a session - /// @param[in] direction The direction of the session - /// @param[in] data Data buffer (will be moved into the session) - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] totalMessageSize The total size of the message in bytes - /// @param[in] totalNumberOfPackets The total number of packets that will be sent or received in this session - /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] sessionCompleteCallback A callback for when the session completes - /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - TransportProtocolSession(Direction direction, - std::unique_ptr data, - std::uint32_t parameterGroupNumber, - std::uint16_t totalMessageSize, - std::uint8_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer); - StateMachineState state = StateMachineState::None; ///< The state machine state for this session - Direction direction; ///< The direction of the session - std::uint32_t parameterGroupNumber; ///< The PGN of the message - std::unique_ptr data; ///< The data buffer for the message - std::uint16_t totalMessageSize; ///< The total size of the message in bytes - std::shared_ptr source; ///< The source control function - std::shared_ptr destination; ///< The destination control function - - std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts std::uint8_t lastSequenceNumber = 0; ///< The last processed sequence number for this set of packets std::uint8_t lastAcknowledgedPacketNumber = 0; ///< The last acknowledged packet number by the receiver - - std::uint8_t totalNumberOfPackets; ///< The total number of packets that will be sent or received in this session std::uint8_t clearToSendPacketCount = 0; ///< The number of packets to be sent in response to one CTS std::uint8_t clearToSendPacketCountMax = 0xFF; ///< The max packets that can be sent per CTS as indicated by the RTS message - - TransmitCompleteCallback sessionCompleteCallback = nullptr; ///< A callback that is to be called when the session is completed - void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr }; static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 16; ///< TP.CM_RTS Multiplexor @@ -252,11 +186,11 @@ namespace isobus static constexpr std::uint32_t BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR = 32; ///< TP.BAM Multiplexor static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 255; ///< Abort multiplexor static constexpr std::uint32_t MAX_PROTOCOL_DATA_LENGTH = 1785; ///< The max number of bytes that this protocol can transfer - static constexpr std::uint32_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard - static constexpr std::uint32_t T2_T3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard - static constexpr std::uint32_t T4_TIMEOUT_MS = 1050; ///< The t4 timeout as defined by the standard + static constexpr std::uint16_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard + static constexpr std::uint16_t T2_T3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard + static constexpr std::uint16_t T4_TIMEOUT_MS = 1050; ///< The t4 timeout as defined by the standard + static constexpr std::uint8_t R_TIMEOUT_MS = 200; ///< The Tr Timeout as defined by the standard static constexpr std::uint8_t SEQUENCE_NUMBER_DATA_INDEX = 0; ///< The index of the sequence number in a frame - static constexpr std::uint8_t MESSAGE_TR_TIMEOUT_MS = 200; ///< The Tr Timeout as defined by the standard static constexpr std::uint8_t PROTOCOL_BYTES_PER_FRAME = 7; ///< The number of payload bytes per frame minus overhead of sequence number /// @brief The constructor for the TransportProtocolManager, for advanced use only. diff --git a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp new file mode 100644 index 00000000..4bd01401 --- /dev/null +++ b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp @@ -0,0 +1,137 @@ +//================================================================================================ +/// @file can_transport_protocol_base.hpp +/// +/// @brief Abstract base class for CAN transport protocols. +/// @author Daan Steenbergen +/// +/// @copyright 2023 The Open-Agriculture Developers +//================================================================================================ + +#ifndef CAN_TRANSPORT_PROTOCOL_BASE_HPP +#define CAN_TRANSPORT_PROTOCOL_BASE_HPP + +#include "isobus/isobus/can_control_function.hpp" +#include "isobus/isobus/can_message.hpp" +#include "isobus/isobus/can_message_data.hpp" + +namespace isobus +{ + //================================================================================================ + /// @class TransportProtocolSessionBase + /// + /// @brief An object to keep track of session information internally + //================================================================================================ + class TransportProtocolSessionBase + { + public: + /// @brief Enumerates the possible session directions + enum class Direction + { + Transmit, ///< We are transmitting a message + Receive ///< We are receiving a message + }; + + /// @brief The constructor for a session + /// @param[in] direction The direction of the session + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + TransportProtocolSessionBase(TransportProtocolSessionBase::Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); + + /// @brief The move constructor + /// @param[in] other The object to move + TransportProtocolSessionBase(TransportProtocolSessionBase &&other) = default; + + /// @brief The move assignment operator + /// @param[in] other The object to move + /// @return A reference to the moved object + TransportProtocolSessionBase &operator=(TransportProtocolSessionBase &&other) = default; + + /// @brief The destructor for a session + virtual ~TransportProtocolSessionBase() = default; + + /// @brief Get the direction of the session + /// @return The direction of the session + Direction get_direction() const; + + /// @brief A useful way to compare session objects to each other for equality, + /// @details A session is considered equal when the source and destination control functions + /// and parameter group number match. Note that we don't compare the super class, + /// so this should only be used to compare sessions of the same type. + /// @param[in] obj The object to compare to + /// @returns true if the objects are equal, false if not + bool operator==(const TransportProtocolSessionBase &obj) const; + + /// @brief Checks if the source and destination control functions match the given control functions. + /// @param[in] other_source The control function to compare with the source control function. + /// @param[in] other_destination The control function to compare with the destination control function. + /// @returns True if the source and destination control functions match the given control functions, false otherwise. + bool matches(std::shared_ptr other_source, std::shared_ptr other_destination) const; + + /// @brief Get the data buffer for the session + /// @return The data buffer for the session + CANMessageData &get_data() const; + + /// @brief Get the total number of bytes that will be sent or received in this session + /// @return The length of the message in number of bytes + std::uint32_t get_message_length() const; + + /// @brief Get the number of bytes that have been sent or received in this session + /// @return The number of bytes that have been sent or received + virtual std::uint32_t get_total_bytes_transferred() const = 0; + + /// @brief Get the percentage of bytes that have been sent or received in this session + /// @return The percentage of bytes that have been sent or received + virtual float get_percentage_bytes_transferred() const = 0; + + /// @brief Get the control function that is sending the message + /// @return The source control function + std::shared_ptr get_source() const; + + /// @brief Get the control function that is receiving the message + /// @return The destination control function + std::shared_ptr get_destination() const; + + /// @brief Get the parameter group number of the message + /// @return The PGN of the message + std::uint32_t get_parameter_group_number() const; + + protected: + /// @brief Update the timestamp of the session + void update_timestamp(); + + /// @brief Get the time that has passed since the last update of the timestamp + /// @return The duration in milliseconds + std::uint32_t get_time_since_last_update() const; + + /// @brief Complete the session + /// @param[in] success True if the session was successful, false otherwise + void complete(bool success) const; + + private: + Direction direction; ///< The direction of the session + std::uint32_t parameterGroupNumber; ///< The PGN of the message + std::unique_ptr data; ///< The data buffer for the message + std::shared_ptr source; ///< The source control function + std::shared_ptr destination; ///< The destination control function + std::uint32_t timestamp_ms = 0; ///< A timestamp used to track session timeouts + + std::uint32_t totalMessageSize; ///< The total size of the message in bytes (the maximum size of a message is from ETP and can fit in an uint32_t) + + TransmitCompleteCallback sessionCompleteCallback = nullptr; ///< A callback that is to be called when the session is completed + void *parent = nullptr; ///< A generic context variable that helps identify what object callbacks are destined for. Can be nullptr + }; +}; // namespace isobus + +#endif // CAN_TRANSPORT_PROTOCOL_BASE_HPP diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index 45d584ef..99a2098a 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -5,7 +5,7 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus/isobus/can_extended_transport_protocol.hpp" @@ -22,42 +22,29 @@ namespace isobus { - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::ExtendedTransportProtocolSession(Direction direction, - std::unique_ptr data, - std::uint32_t parameterGroupNumber, - std::uint32_t totalMessageSize, - std::uint32_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer) : - direction(direction), - parameterGroupNumber(parameterGroupNumber), - data(std::move(data)), - totalMessageSize(totalMessageSize), - source(source), - destination(destination), - totalNumberOfPackets(totalNumberOfPackets), - clearToSendPacketCountMax(clearToSendPacketMax), - sessionCompleteCallback(sessionCompleteCallback), - parent(parentPointer) + // Explicitly define the move constructor and assignment operator to ensure that the base class is moved correctly + // See https://stackoverflow.com/a/15351528 for why this is necessary + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::ExtendedTransportProtocolSession(ExtendedTransportProtocolManager::ExtendedTransportProtocolSession &&obj) noexcept : + TransportProtocolSessionBase(std::move(obj)), + state(obj.state), + lastSequenceNumber(obj.lastSequenceNumber), + sequenceNumberOffset(obj.sequenceNumberOffset), + lastAcknowledgedPacketNumber(obj.lastAcknowledgedPacketNumber), + dataPacketOffsetPacketCount(obj.dataPacketOffsetPacketCount), + clearToSendPacketCountLimit(obj.clearToSendPacketCountLimit) { } - bool ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::operator==(const ExtendedTransportProtocolSession &obj) const + ExtendedTransportProtocolManager::ExtendedTransportProtocolSession &ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::operator=(ExtendedTransportProtocolSession &&obj) noexcept { - return ((source == obj.source) && (destination == obj.destination) && (parameterGroupNumber == obj.parameterGroupNumber)); - } - - bool ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::matches(std::shared_ptr other_source, std::shared_ptr other_destination) const - { - return ((source == other_source) && (destination == other_destination)); - } - - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::Direction ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_direction() const - { - return direction; + TransportProtocolSessionBase::operator=(std::move(obj)); + state = obj.state; + lastSequenceNumber = obj.lastSequenceNumber; + sequenceNumberOffset = obj.sequenceNumberOffset; + lastAcknowledgedPacketNumber = obj.lastAcknowledgedPacketNumber; + dataPacketOffsetPacketCount = obj.dataPacketOffsetPacketCount; + clearToSendPacketCountLimit = obj.clearToSendPacketCountLimit; + return *this; } ExtendedTransportProtocolManager::StateMachineState ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_state() const @@ -65,109 +52,48 @@ namespace isobus return state; } - std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_message_length() const - { - return totalMessageSize; - } - - CANMessageData &ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_data() const - { - return *data; - } - - std::shared_ptr ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_source() const - { - return source; - } - - std::shared_ptr ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_destination() const - { - return destination; - } - - std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_parameter_group_number() const - { - return parameterGroupNumber; - } - - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::create_receive_session(std::uint32_t parameterGroupNumber, - std::uint32_t totalMessageSize, - std::uint32_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination) + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_state(StateMachineState value) { - return ExtendedTransportProtocolSession(ExtendedTransportProtocolSession::Direction::Receive, - std::unique_ptr(new CANMessageDataVector(totalMessageSize)), - parameterGroupNumber, - totalMessageSize, - totalNumberOfPackets, - clearToSendPacketMax, - source, - destination, - nullptr, - nullptr); + state = value; + update_timestamp(); } - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::create_transmit_session(std::uint32_t parameterGroupNumber, - std::unique_ptr data, - std::shared_ptr source, - std::shared_ptr destination, - std::uint8_t clearToSendPacketMax, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer) + std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_total_bytes_transferred() const { - auto totalMessageSize = static_cast(data->size()); - auto totalNumberOfPackets = totalMessageSize / PROTOCOL_BYTES_PER_FRAME; - // Because integer division rounds down, we need to add one if there is a remainder - if (0 != (totalMessageSize % PROTOCOL_BYTES_PER_FRAME)) - { - totalNumberOfPackets++; - } - return ExtendedTransportProtocolSession(ExtendedTransportProtocolSession::Direction::Transmit, - std::move(data), - parameterGroupNumber, - totalMessageSize, - totalNumberOfPackets, - clearToSendPacketMax, - source, - destination, - sessionCompleteCallback, - parentPointer); + return get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; } - void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_state(StateMachineState value) + float ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_percentage_bytes_transferred() const { - state = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()); } std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_dpo_number_of_packets_remaining() const { auto packetsSinceDPO = static_cast(get_last_packet_number() - lastAcknowledgedPacketNumber); - return clearToSendPacketCount - packetsSinceDPO; + return dataPacketOffsetPacketCount - packetsSinceDPO; } void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_dpo_number_of_packets(std::uint8_t value) { - clearToSendPacketCount = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + dataPacketOffsetPacketCount = value; + update_timestamp(); } std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_dpo_number_of_packets() const { - return clearToSendPacketCount; + return dataPacketOffsetPacketCount; } std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_cts_number_of_packet_limit() const { - return clearToSendPacketCountMax; + return clearToSendPacketCountLimit; } void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_cts_number_of_packet_limit(std::uint8_t value) { - clearToSendPacketCountMax = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + clearToSendPacketCountLimit = value; + update_timestamp(); } std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_last_sequence_number() const @@ -183,13 +109,13 @@ namespace isobus void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_last_sequency_number(std::uint8_t value) { lastSequenceNumber = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + update_timestamp(); } void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_acknowledged_packet_number(std::uint32_t value) { lastAcknowledgedPacketNumber = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + update_timestamp(); } std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_last_acknowledged_packet_number() const @@ -199,11 +125,16 @@ namespace isobus std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_number_of_remaining_packets() const { - return totalNumberOfPackets - get_last_packet_number(); + return get_total_number_of_packets() - get_last_packet_number(); } std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_total_number_of_packets() const { + auto totalNumberOfPackets = get_message_length() / PROTOCOL_BYTES_PER_FRAME; + if ((get_message_length() % PROTOCOL_BYTES_PER_FRAME) > 0) + { + totalNumberOfPackets++; + } return totalNumberOfPackets; } @@ -244,20 +175,15 @@ namespace isobus } } - auto data = std::unique_ptr(new CANMessageDataVector(totalMessageSize)); - auto totalNumberOfPackets = totalMessageSize / PROTOCOL_BYTES_PER_FRAME; - // Because integer division rounds down, we need to add one if there is a remainder - if (0 != (totalMessageSize % PROTOCOL_BYTES_PER_FRAME)) - { - totalNumberOfPackets++; - } + ExtendedTransportProtocolSession newSession(ExtendedTransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + source, + destination, + nullptr, // No callback + nullptr); - ExtendedTransportProtocolSession newSession = ExtendedTransportProtocolSession::create_receive_session(parameterGroupNumber, - totalMessageSize, - totalNumberOfPackets, - 0xFF, - source, - destination); newSession.set_state(StateMachineState::SendClearToSend); activeSessions.push_back(std::move(newSession)); } @@ -362,7 +288,7 @@ namespace isobus void ExtendedTransportProtocolManager::process_end_of_session_acknowledgement(const std::shared_ptr source, const std::shared_ptr destination, std::uint32_t parameterGroupNumber, - std::uint32_t) + std::uint32_t numberOfBytesTransferred) { auto session = get_session(destination, source); if (nullptr != session) @@ -371,7 +297,8 @@ namespace isobus { CANStackLogger::debug("[ETP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); session->state = StateMachineState::None; - close_session(*session, true); + bool successful = (numberOfBytesTransferred == session->get_message_length()); + close_session(*session, successful); } else { @@ -612,15 +539,16 @@ namespace isobus // We can handle this message! If we only have a view of the data, let's clone the data, // so we don't have to worry about it being deleted. data = data->copy_if_not_owned(std::move(data)); - - ExtendedTransportProtocolSession session = ExtendedTransportProtocolSession::create_transmit_session(parameterGroupNumber, - std::move(data), - source, - destination, - configuration->get_number_of_packets_per_cts_message(), - sessionCompleteCallback, - parentPointer); - + auto dataLength = static_cast(data->size()); + + ExtendedTransportProtocolSession session(ExtendedTransportProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + dataLength, + source, + destination, + sessionCompleteCallback, + parentPointer); session.set_state(StateMachineState::SendRequestToSend); CANStackLogger::debug("[ETP]: New tx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, @@ -725,7 +653,7 @@ namespace isobus case StateMachineState::WaitForClearToSend: { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout rx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); if (session.get_dpo_number_of_packets() > 0) @@ -753,7 +681,7 @@ namespace isobus case StateMachineState::WaitForDataPacketOffset: { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected DPO)", session.get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); @@ -778,7 +706,7 @@ namespace isobus case StateMachineState::WaitForDataTransferPacket: { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T1_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T1_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); abort_session(session, ConnectionAbortReason::Timeout); @@ -788,7 +716,7 @@ namespace isobus case StateMachineState::WaitForEndOfMessageAcknowledge: { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); @@ -846,19 +774,7 @@ namespace isobus void ExtendedTransportProtocolManager::close_session(const ExtendedTransportProtocolSession &session, bool successful) { - if ((nullptr != session.sessionCompleteCallback) && (ExtendedTransportProtocolSession::Direction::Transmit == session.get_direction())) - { - if (auto source = session.get_source()) - { - session.sessionCompleteCallback(session.get_parameter_group_number(), - session.get_message_length(), - std::static_pointer_cast(source), - session.get_destination(), - successful, - session.parent); - } - } - + session.complete(successful); auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); if (activeSessions.end() != sessionLocation) { diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index 3b2488f0..51997050 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -6,7 +6,7 @@ /// @author Adrian Del Grosso /// @author Daan Steenbergen /// -/// @copyright 2022 Adrian Del Grosso +/// @copyright 2023 The Open-Agriculture Developers //================================================================================================ #include "isobus/isobus/can_transport_protocol.hpp" @@ -23,42 +23,33 @@ namespace isobus { - TransportProtocolManager::TransportProtocolSession::TransportProtocolSession(Direction direction, - std::unique_ptr data, - std::uint32_t parameterGroupNumber, - std::uint16_t totalMessageSize, - std::uint8_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer) : - direction(direction), - parameterGroupNumber(parameterGroupNumber), - data(std::move(data)), - totalMessageSize(totalMessageSize), - source(source), - destination(destination), - totalNumberOfPackets(totalNumberOfPackets), - clearToSendPacketCountMax(clearToSendPacketMax), - sessionCompleteCallback(sessionCompleteCallback), - parent(parentPointer) + TransportProtocolManager::TransportProtocolSession::TransportProtocolSession(Direction direction, std::unique_ptr data, std::uint32_t parameterGroupNumber, std::uint16_t totalMessageSize, std::uint8_t clearToSendPacketMax, std::shared_ptr source, std::shared_ptr destination, TransmitCompleteCallback sessionCompleteCallback, void *parentPointer) : + TransportProtocolSessionBase(direction, std::move(data), parameterGroupNumber, totalMessageSize, source, destination, sessionCompleteCallback, parentPointer), + clearToSendPacketCountMax(clearToSendPacketMax) { } - bool TransportProtocolManager::TransportProtocolSession::operator==(const TransportProtocolSession &obj) const + // Explicitly define the move constructor and assignment operator to ensure that the base class is moved correctly + // See https://stackoverflow.com/a/15351528 for why this is necessary + TransportProtocolManager::TransportProtocolSession::TransportProtocolSession(TransportProtocolManager::TransportProtocolSession &&obj) noexcept : + TransportProtocolSessionBase(std::move(obj)), + state(obj.state), + lastSequenceNumber(obj.lastSequenceNumber), + lastAcknowledgedPacketNumber(obj.lastAcknowledgedPacketNumber), + clearToSendPacketCount(obj.clearToSendPacketCount), + clearToSendPacketCountMax(obj.clearToSendPacketCountMax) { - return ((source == obj.source) && (destination == obj.destination) && (parameterGroupNumber == obj.parameterGroupNumber)); } - bool TransportProtocolManager::TransportProtocolSession::matches(std::shared_ptr other_source, std::shared_ptr other_destination) const + TransportProtocolManager::TransportProtocolSession &TransportProtocolManager::TransportProtocolSession::operator=(TransportProtocolSession &&obj) noexcept { - return ((source == other_source) && (destination == other_destination)); - } - - TransportProtocolManager::TransportProtocolSession::Direction TransportProtocolManager::TransportProtocolSession::get_direction() const - { - return direction; + TransportProtocolSessionBase::operator=(std::move(obj)); + clearToSendPacketCountMax = obj.clearToSendPacketCountMax; + clearToSendPacketCount = obj.clearToSendPacketCount; + lastSequenceNumber = obj.lastSequenceNumber; + lastAcknowledgedPacketNumber = obj.lastAcknowledgedPacketNumber; + state = obj.state; + return *this; } TransportProtocolManager::StateMachineState TransportProtocolManager::TransportProtocolSession::get_state() const @@ -66,85 +57,31 @@ namespace isobus return state; } - std::uint32_t TransportProtocolManager::TransportProtocolSession::get_message_length() const - { - return totalMessageSize; - } - - CANMessageData &TransportProtocolManager::TransportProtocolSession::get_data() const - { - return *data; - } - - std::shared_ptr TransportProtocolManager::TransportProtocolSession::get_source() const - { - return source; - } - - std::shared_ptr TransportProtocolManager::TransportProtocolSession::get_destination() const + std::uint16_t TransportProtocolManager::TransportProtocolSession::get_message_length() const { - return destination; - } - - std::uint32_t TransportProtocolManager::TransportProtocolSession::get_parameter_group_number() const - { - return parameterGroupNumber; + // We know that this session can only be used to transfer 1785 bytes of data, so we can safely cast to a uint16_t + return static_cast(TransportProtocolSessionBase::get_message_length()); } bool TransportProtocolManager::TransportProtocolSession::is_broadcast() const { - return (nullptr == destination); + return (nullptr == get_destination()); } - TransportProtocolManager::TransportProtocolSession TransportProtocolManager::TransportProtocolSession::create_receive_session(std::uint32_t parameterGroupNumber, - std::uint16_t totalMessageSize, - std::uint8_t totalNumberOfPackets, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination) + std::uint32_t TransportProtocolManager::TransportProtocolSession::get_total_bytes_transferred() const { - return TransportProtocolSession(TransportProtocolSession::Direction::Receive, - std::unique_ptr(new CANMessageDataVector(totalMessageSize)), - parameterGroupNumber, - totalMessageSize, - totalNumberOfPackets, - clearToSendPacketMax, - source, - destination, - nullptr, - nullptr); + return get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; } - TransportProtocolManager::TransportProtocolSession TransportProtocolManager::TransportProtocolSession::create_transmit_session(std::uint32_t parameterGroupNumber, - std::unique_ptr data, - std::shared_ptr source, - std::shared_ptr destination, - std::uint8_t clearToSendPacketMax, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer) + float TransportProtocolManager::TransportProtocolSession::get_percentage_bytes_transferred() const { - auto totalMessageSize = static_cast(data->size()); - auto totalPacketCount = static_cast(totalMessageSize / PROTOCOL_BYTES_PER_FRAME); - if (0 != (totalMessageSize % PROTOCOL_BYTES_PER_FRAME)) - { - totalPacketCount++; - } - return TransportProtocolSession(TransportProtocolSession::Direction::Transmit, - std::move(data), - parameterGroupNumber, - totalMessageSize, - totalPacketCount, - clearToSendPacketMax, - source, - destination, - sessionCompleteCallback, - parentPointer); + return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()); } void TransportProtocolManager::TransportProtocolSession::set_state(StateMachineState value) { state = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + update_timestamp(); } std::uint8_t TransportProtocolManager::TransportProtocolSession::get_cts_number_of_packets_remaining() const @@ -156,7 +93,7 @@ namespace isobus void TransportProtocolManager::TransportProtocolSession::set_cts_number_of_packets(std::uint8_t value) { clearToSendPacketCount = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + update_timestamp(); } std::uint8_t TransportProtocolManager::TransportProtocolSession::get_cts_number_of_packets() const @@ -182,22 +119,27 @@ namespace isobus void TransportProtocolManager::TransportProtocolSession::set_last_sequency_number(std::uint8_t value) { lastSequenceNumber = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + update_timestamp(); } void TransportProtocolManager::TransportProtocolSession::set_acknowledged_packet_number(std::uint8_t value) { lastAcknowledgedPacketNumber = value; - timestamp_ms = SystemTiming::get_timestamp_ms(); + update_timestamp(); } std::uint8_t TransportProtocolManager::TransportProtocolSession::get_number_of_remaining_packets() const { - return totalNumberOfPackets - get_last_packet_number(); + return get_total_number_of_packets() - get_last_packet_number(); } std::uint8_t TransportProtocolManager::TransportProtocolSession::get_total_number_of_packets() const { + auto totalNumberOfPackets = static_cast(get_message_length() / PROTOCOL_BYTES_PER_FRAME); + if ((get_message_length() % PROTOCOL_BYTES_PER_FRAME) > 0) + { + totalNumberOfPackets++; + } return totalNumberOfPackets; } @@ -221,6 +163,10 @@ namespace isobus // TODO: consider using maximum memory instead of maximum number of sessions CANStackLogger::warn("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, configured maximum number of sessions reached.", parameterGroupNumber); } + else if (totalMessageSize > MAX_PROTOCOL_DATA_LENGTH) + { + CANStackLogger::warn("[TP]: Ignoring Broadcast Announcement Message (BAM) for 0x%05X, message size (%hu) is greater than the maximum (%hu).", parameterGroupNumber, totalMessageSize, MAX_PROTOCOL_DATA_LENGTH); + } else { auto oldSession = get_session(source, nullptr); @@ -230,18 +176,27 @@ namespace isobus close_session(*oldSession, false); } - auto data = std::unique_ptr(new CANMessageDataVector(totalMessageSize)); + TransportProtocolSession newSession(TransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + 0xFF, // Arbitrary - unused for broadcast + source, + nullptr, // Global destination + nullptr, // No callback + nullptr); - TransportProtocolSession newSession = TransportProtocolSession::create_receive_session(parameterGroupNumber, - totalMessageSize, - totalNumberOfPackets, - 0xFF, // Arbitrary - unused for broadcast - source, - nullptr); // Global destination - newSession.set_state(StateMachineState::WaitForDataTransferPacket); - activeSessions.push_back(std::move(newSession)); + if (newSession.get_total_number_of_packets() != totalNumberOfPackets) + { + CANStackLogger::warn("[TP]: Received Broadcast Announcement Message (BAM) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); + } + else + { + newSession.set_state(StateMachineState::WaitForDataTransferPacket); + activeSessions.push_back(std::move(newSession)); - CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); + CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); + } } } @@ -283,14 +238,26 @@ namespace isobus clearToSendPacketMax = configuration->get_number_of_packets_per_cts_message(); } - TransportProtocolSession newSession = TransportProtocolSession::create_receive_session(parameterGroupNumber, - totalMessageSize, - totalNumberOfPackets, - clearToSendPacketMax, - source, - destination); - newSession.set_state(StateMachineState::SendClearToSend); - activeSessions.push_back(std::move(newSession)); + TransportProtocolSession newSession(TransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + clearToSendPacketMax, + source, + destination, + nullptr, // No callback + nullptr); + + if (newSession.get_total_number_of_packets() != totalNumberOfPackets) + { + CANStackLogger::error("[TP]: Received Request To Send (RTS) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); + abort_session(newSession, ConnectionAbortReason::AnyOtherError); + } + else + { + newSession.set_state(StateMachineState::SendClearToSend); + activeSessions.push_back(std::move(newSession)); + } } } @@ -639,14 +606,17 @@ namespace isobus // We can handle this message! If we only have a view of the data, let's clone the data, // so we don't have to worry about it being deleted. data = data->copy_if_not_owned(std::move(data)); - - TransportProtocolSession session = TransportProtocolSession::create_transmit_session(parameterGroupNumber, - std::move(data), - source, - destination, - configuration->get_number_of_packets_per_cts_message(), - sessionCompleteCallback, - parentPointer); + auto dataLength = static_cast(data->size()); + + TransportProtocolSession session(TransportProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + dataLength, + configuration->get_number_of_packets_per_cts_message(), + source, + destination, + sessionCompleteCallback, + parentPointer); if (session.is_broadcast()) { @@ -784,7 +754,7 @@ namespace isobus case StateMachineState::WaitForClearToSend: { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout rx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); if (session.get_cts_number_of_packets() > 0) @@ -812,7 +782,7 @@ namespace isobus case StateMachineState::SendDataTransferPackets: { - if (session.is_broadcast() && (!SystemTiming::time_expired_ms(session.timestamp_ms, configuration->get_minimum_time_between_transport_protocol_bam_frames()))) + if (session.is_broadcast() && (session.get_time_since_last_update() < configuration->get_minimum_time_between_transport_protocol_bam_frames())) { // Need to wait before sending the next data frame of the broadcast session } @@ -828,7 +798,7 @@ namespace isobus if (session.is_broadcast()) { // Broadcast message timeout check - if (SystemTiming::time_expired_ms(session.timestamp_ms, T1_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T1_TIMEOUT_MS) { CANStackLogger::warn("[TP]: Broadcast rx session timeout"); close_session(session, false); @@ -837,7 +807,7 @@ namespace isobus else if (session.get_cts_number_of_packets_remaining() == session.get_cts_number_of_packets()) { // Waiting to receive the first data frame after CTS - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected first data frame)"); abort_session(session, ConnectionAbortReason::Timeout); @@ -846,7 +816,7 @@ namespace isobus else { // Waiting on sequencial data frames - if (SystemTiming::time_expired_ms(session.timestamp_ms, T1_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T1_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); abort_session(session, ConnectionAbortReason::Timeout); @@ -857,7 +827,7 @@ namespace isobus case StateMachineState::WaitForEndOfMessageAcknowledge: { - if (SystemTiming::time_expired_ms(session.timestamp_ms, T2_T3_TIMEOUT_MS)) + if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); @@ -915,18 +885,7 @@ namespace isobus void TransportProtocolManager::close_session(const TransportProtocolSession &session, bool successful) { - if ((nullptr != session.sessionCompleteCallback) && (TransportProtocolSession::Direction::Transmit == session.get_direction())) - { - if (auto source = session.get_source()) - { - session.sessionCompleteCallback(session.get_parameter_group_number(), - session.get_message_length(), - std::static_pointer_cast(source), - session.get_destination(), - successful, - session.parent); - } - } + session.complete(successful); auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); if (activeSessions.end() != sessionLocation) @@ -938,124 +897,102 @@ namespace isobus bool TransportProtocolManager::send_broadcast_announce_message(const TransportProtocolSession &session) const { - bool retVal = false; - if (auto source = session.get_source()) - { - const std::array buffer{ - BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - session.get_total_number_of_packets(), - 0xFF, - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(source), - nullptr, - CANIdentifier::CANPriority::PriorityLowest7); - } - return retVal; + const std::array buffer{ + BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + session.get_total_number_of_packets(), + 0xFF, + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_source()), + nullptr, + CANIdentifier::CANPriority::PriorityLowest7); } bool TransportProtocolManager::send_request_to_send(const TransportProtocolSession &session) const { - bool retVal = false; - if (auto source = session.get_source()) - { - const std::array buffer{ - REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - session.get_total_number_of_packets(), - session.get_rts_number_of_packet_limit(), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(source), - session.get_destination(), - CANIdentifier::CANPriority::PriorityLowest7); - } - return retVal; + const std::array buffer{ + REQUEST_TO_SEND_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + session.get_total_number_of_packets(), + session.get_rts_number_of_packet_limit(), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_source()), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7); } bool TransportProtocolManager::send_clear_to_send(TransportProtocolSession &session) const { bool retVal = false; - // Since we're the receiving side, we are the destination of the session - if (auto ourControlFunction = session.get_destination()) + + std::uint8_t packetsThisSegment = session.get_number_of_remaining_packets(); + if (packetsThisSegment > session.get_rts_number_of_packet_limit()) { - std::uint8_t packetsThisSegment = session.get_number_of_remaining_packets(); - if (packetsThisSegment > session.get_rts_number_of_packet_limit()) - { - packetsThisSegment = session.get_rts_number_of_packet_limit(); - } - else if (packetsThisSegment > 16) - { - //! @todo apply CTS number of packets recommendation of 16 via a configuration option - packetsThisSegment = 16; - } + packetsThisSegment = session.get_rts_number_of_packet_limit(); + } + else if (packetsThisSegment > 16) + { + //! @todo apply CTS number of packets recommendation of 16 via a configuration option + packetsThisSegment = 16; + } - const std::array buffer{ - CLEAR_TO_SEND_MULTIPLEXOR, - packetsThisSegment, - static_cast(session.get_last_packet_number() + 1), - 0xFF, - 0xFF, - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(ourControlFunction), - session.get_source(), - CANIdentifier::CANPriority::PriorityLowest7); - if (retVal) - { - session.set_cts_number_of_packets(packetsThisSegment); - session.set_acknowledged_packet_number(session.get_last_packet_number()); - } + const std::array buffer{ + CLEAR_TO_SEND_MULTIPLEXOR, + packetsThisSegment, + static_cast(session.get_last_packet_number() + 1), + 0xFF, + 0xFF, + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + if (retVal) + { + session.set_cts_number_of_packets(packetsThisSegment); + session.set_acknowledged_packet_number(session.get_last_packet_number()); } return retVal; } bool TransportProtocolManager::send_end_of_session_acknowledgement(const TransportProtocolSession &session) const { - bool retVal = false; - // Since we're the receiving side, we are the destination of the session - if (auto ourControlFunction = session.get_destination()) - { - std::uint32_t messageLength = session.get_message_length(); - std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); - - const std::array buffer{ - END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, - static_cast(messageLength & 0xFF), - static_cast((messageLength >> 8) & 0xFF), - session.get_total_number_of_packets(), - 0xFF, - static_cast(parameterGroupNumber & 0xFF), - static_cast((parameterGroupNumber >> 8) & 0xFF), - static_cast((parameterGroupNumber >> 16) & 0xFF), - }; - - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(ourControlFunction), - session.get_source(), - CANIdentifier::CANPriority::PriorityLowest7); - } - else - { - CANStackLogger::warn("[TP]: Attempted to send EOM to null session"); - } - return retVal; + std::uint32_t messageLength = session.get_message_length(); + std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); + + const std::array buffer{ + END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, + static_cast(messageLength & 0xFF), + static_cast((messageLength >> 8) & 0xFF), + session.get_total_number_of_packets(), + 0xFF, + static_cast(parameterGroupNumber & 0xFF), + static_cast((parameterGroupNumber >> 8) & 0xFF), + static_cast((parameterGroupNumber >> 16) & 0xFF), + }; + + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session + + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); } bool TransportProtocolManager::has_session(std::shared_ptr source, std::shared_ptr destination) diff --git a/isobus/src/can_transport_protocol_base.cpp b/isobus/src/can_transport_protocol_base.cpp new file mode 100644 index 00000000..4c86b2ab --- /dev/null +++ b/isobus/src/can_transport_protocol_base.cpp @@ -0,0 +1,98 @@ +//================================================================================================ +/// @file can_transport_protocol.cpp +/// +/// @brief Abstract base class for CAN transport protocols. +/// @author Daan Steenbergen +/// +/// @copyright 2023 The Open-Agriculture Developers +//================================================================================================ + +#include "isobus/isobus/can_transport_protocol_base.hpp" +#include "isobus/isobus/can_internal_control_function.hpp" +#include "isobus/utility/system_timing.hpp" + +namespace isobus +{ + TransportProtocolSessionBase::TransportProtocolSessionBase(TransportProtocolSessionBase::Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint32_t totalMessageSize, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer) : + direction(direction), + parameterGroupNumber(parameterGroupNumber), + data(std::move(data)), + source(source), + destination(destination), + totalMessageSize(totalMessageSize), + sessionCompleteCallback(sessionCompleteCallback), + parent(parentPointer) + { + } + + bool TransportProtocolSessionBase::operator==(const TransportProtocolSessionBase &obj) const + { + return ((source == obj.source) && (destination == obj.destination) && (parameterGroupNumber == obj.parameterGroupNumber)); + } + + bool TransportProtocolSessionBase::matches(std::shared_ptr other_source, std::shared_ptr other_destination) const + { + return ((source == other_source) && (destination == other_destination)); + } + + TransportProtocolSessionBase::Direction TransportProtocolSessionBase::get_direction() const + { + return direction; + } + + CANMessageData &TransportProtocolSessionBase::get_data() const + { + return *data; + } + + std::uint32_t TransportProtocolSessionBase::get_message_length() const + { + return totalMessageSize; + } + + std::shared_ptr TransportProtocolSessionBase::get_source() const + { + return source; + } + + std::shared_ptr TransportProtocolSessionBase::get_destination() const + { + return destination; + } + + std::uint32_t TransportProtocolSessionBase::get_parameter_group_number() const + { + return parameterGroupNumber; + } + + void isobus::TransportProtocolSessionBase::update_timestamp() + { + timestamp_ms = SystemTiming::get_timestamp_ms(); + } + + std::uint32_t TransportProtocolSessionBase::get_time_since_last_update() const + { + return SystemTiming::get_time_elapsed_ms(timestamp_ms); + } + + void TransportProtocolSessionBase::complete(bool success) const + { + if ((nullptr != sessionCompleteCallback) && (Direction::Transmit == direction)) + { + sessionCompleteCallback(get_parameter_group_number(), + get_message_length(), + std::static_pointer_cast(source), + get_destination(), + success, + parent); + } + } + +} \ No newline at end of file From c81a52291200802b42aa9f998b95d71c45eb4185 Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Wed, 20 Dec 2023 20:11:27 +0100 Subject: [PATCH 4/9] example(transport): add basic consecutive self-receiving messages sample --- examples/transport_layer/main.cpp | 227 +++++++++------- .../can_extended_transport_protocol.hpp | 15 +- .../isobus/isobus/can_network_manager.hpp | 10 +- .../isobus/isobus/can_transport_protocol.hpp | 7 +- .../isobus/can_transport_protocol_base.hpp | 2 +- .../src/can_extended_transport_protocol.cpp | 255 ++++++++++-------- isobus/src/can_network_manager.cpp | 85 ++++-- isobus/src/can_transport_protocol.cpp | 33 ++- test/transport_protocol_tests.cpp | 2 +- 9 files changed, 374 insertions(+), 262 deletions(-) diff --git a/examples/transport_layer/main.cpp b/examples/transport_layer/main.cpp index 54e664fa..4f6e9575 100644 --- a/examples/transport_layer/main.cpp +++ b/examples/transport_layer/main.cpp @@ -1,20 +1,19 @@ -#include "isobus/hardware_integration/available_can_drivers.hpp" #include "isobus/hardware_integration/can_hardware_interface.hpp" -#include "isobus/hardware_integration/socket_can_interface.hpp" -#include "isobus/isobus/can_general_parameter_group_numbers.hpp" -#include "isobus/isobus/can_network_configuration.hpp" +#include "isobus/hardware_integration/virtual_can_plugin.hpp" #include "isobus/isobus/can_network_manager.hpp" #include "isobus/isobus/can_partnered_control_function.hpp" -#include "isobus/isobus/can_transport_protocol.hpp" #include #include +#include #include -#include #include +#include -static constexpr std::uint16_t MAX_TP_SIZE_BYTES = 1785; -static constexpr std::uint32_t ETP_TEST_SIZE = 2048; +static constexpr std::uint32_t PARAMETER_GROUP_NUMBER = 0xEF00; ///< The parameter group number we will use for testing +static constexpr std::uint16_t MAX_TP_MESSAGE_SIZE_BYTES = 1785; ///< The max number of bytes the Transport Protocol can handle +static constexpr std::uint32_t MAX_ETP_MESSAGE_SIZE_BYTES = 117440505; ///< The max number of bytes the Extended Transport Protocol can handle +static constexpr std::uint32_t MAX_MESSAGE_SIZE_BYTES = 1000000; ///< The max number of bytes we will test sending static std::atomic_bool running = { true }; void signal_handler(int) @@ -22,33 +21,25 @@ void signal_handler(int) running = false; } +using namespace isobus; + +// Forward declarations +void check_can_message(const CANMessage &message, void *); +void print_progress_bar(const TransportProtocolSessionBase *session); + +// The example sends a series of CAN messages of incrementing length to itself and checks that the data is correct int main() { std::signal(SIGINT, signal_handler); - std::shared_ptr canDriver = nullptr; -#if defined(ISOBUS_SOCKETCAN_AVAILABLE) - canDriver = std::make_shared("can0"); -#elif defined(ISOBUS_WINDOWSPCANBASIC_AVAILABLE) - canDriver = std::make_shared(PCAN_USBBUS1); -#elif defined(ISOBUS_WINDOWSINNOMAKERUSB2CAN_AVAILABLE) - canDriver = std::make_shared(0); // CAN0 -#elif defined(ISOBUS_MACCANPCAN_AVAILABLE) - canDriver = std::make_shared(PCAN_USBBUS1); -#elif defined(ISOBUS_SYS_TEC_AVAILABLE) - canDriver = std::make_shared(); -#endif - if (nullptr == canDriver) - { - std::cout << "Unable to find a CAN driver. Please make sure you have one of the above drivers installed with the library." << std::endl; - std::cout << "If you want to use a different driver, please add it to the list above." << std::endl; - return -1; - } + std::shared_ptr originatorDriver = std::make_shared("test-channel"); + std::shared_ptr recipientDriver = std::make_shared("test-channel"); - isobus::CANHardwareInterface::set_number_of_can_channels(1); - isobus::CANHardwareInterface::assign_can_channel_frame_handler(0, canDriver); + CANHardwareInterface::set_number_of_can_channels(2); + CANHardwareInterface::assign_can_channel_frame_handler(0, originatorDriver); + CANHardwareInterface::assign_can_channel_frame_handler(1, recipientDriver); - if ((!isobus::CANHardwareInterface::start()) || (!canDriver->get_is_valid())) + if ((!CANHardwareInterface::start()) || (!originatorDriver->get_is_valid()) || (!recipientDriver->get_is_valid())) { std::cout << "Failed to start hardware interface. The CAN driver might be invalid." << std::endl; return -2; @@ -56,111 +47,151 @@ int main() std::this_thread::sleep_for(std::chrono::milliseconds(250)); - isobus::NAME TestDeviceNAME(0); - - //! Make sure you change these for your device!!!! - TestDeviceNAME.set_arbitrary_address_capable(true); - TestDeviceNAME.set_industry_group(1); - TestDeviceNAME.set_device_class(0); - TestDeviceNAME.set_function_code(static_cast(isobus::NAME::Function::SteeringControl)); - TestDeviceNAME.set_identity_number(2); - TestDeviceNAME.set_ecu_instance(0); - TestDeviceNAME.set_function_instance(0); - TestDeviceNAME.set_device_class_instance(0); - TestDeviceNAME.set_manufacturer_code(1407); - - const isobus::NAMEFilter filterVirtualTerminal(isobus::NAME::NAMEParameters::FunctionCode, static_cast(isobus::NAME::Function::VirtualTerminal)); - - auto TestInternalECU = isobus::InternalControlFunction::create(TestDeviceNAME, 0x1C, 0); - auto TestPartner = isobus::PartneredControlFunction::create(0, { filterVirtualTerminal }); - - // Wait to make sure address claiming is done. The time is arbitrary. - //! @todo Check this instead of asuming it is done - std::this_thread::sleep_for(std::chrono::milliseconds(1250)); - - // Set up some test CAN messages - std::uint8_t TPTestBuffer[MAX_TP_SIZE_BYTES]; - std::uint8_t ETPTestBuffer[ETP_TEST_SIZE]; - - for (uint16_t i = 0; i < MAX_TP_SIZE_BYTES; i++) + NAME originatorNAME(0); + originatorNAME.set_arbitrary_address_capable(true); + originatorNAME.set_industry_group(1); + originatorNAME.set_device_class(0); + originatorNAME.set_function_code(static_cast(NAME::Function::SteeringControl)); + originatorNAME.set_identity_number(2); + originatorNAME.set_ecu_instance(0); + originatorNAME.set_function_instance(0); + originatorNAME.set_device_class_instance(0); + originatorNAME.set_manufacturer_code(1407); + + NAME recipientNAME(0); + recipientNAME.set_arbitrary_address_capable(true); + recipientNAME.set_industry_group(1); + recipientNAME.set_device_class(0); + recipientNAME.set_function_code(static_cast(NAME::Function::VirtualTerminal)); + recipientNAME.set_identity_number(1); + recipientNAME.set_ecu_instance(0); + recipientNAME.set_function_instance(0); + recipientNAME.set_device_class_instance(0); + recipientNAME.set_manufacturer_code(1407); + + const NAMEFilter filterOriginator(NAME::NAMEParameters::FunctionCode, static_cast(NAME::Function::SteeringControl)); + const NAMEFilter filterRecipient(NAME::NAMEParameters::FunctionCode, static_cast(NAME::Function::VirtualTerminal)); + + auto originatorECU = InternalControlFunction::create(originatorNAME, 0x1C, 0); + auto originatorPartner = PartneredControlFunction::create(1, { filterOriginator }); + auto recipientECU = InternalControlFunction::create(recipientNAME, 0x1D, 1); + auto recipientPartner = PartneredControlFunction::create(0, { filterRecipient }); + + // We want to make sure address claiming is successful before continuing + auto addressClaimedFuture = std::async(std::launch::async, [&originatorECU, &recipientECU, &originatorPartner, &recipientPartner]() { + while ((!originatorECU->get_address_valid()) || (!recipientECU->get_address_valid()) || + (!originatorPartner->get_address_valid()) || (!recipientPartner->get_address_valid())) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + }); + if (addressClaimedFuture.wait_for(std::chrono::seconds(5)) == std::future_status::timeout) { - TPTestBuffer[i] = (i % 0xFF); // Fill buffer with junk data + std::cout << "Address claiming failed. Please make sure that your internal control function can claim a valid address." << std::endl; + return -3; } - for (uint32_t i = 0; i < ETP_TEST_SIZE; i++) + + // Set up a message buffer for testing + std::vector sendBuffer; + sendBuffer.reserve(MAX_MESSAGE_SIZE_BYTES); + + for (uint32_t i = 0; i < MAX_MESSAGE_SIZE_BYTES; i++) { - ETPTestBuffer[i] = (i % 0xFF); // Fill buffer with junk data + sendBuffer.push_back(i % 0xFF); // Fill buffer with incrementing values } + // Register a callback for receiving CAN messages + CANNetworkManager::CANNetwork.add_global_parameter_group_number_callback(PARAMETER_GROUP_NUMBER, check_can_message, nullptr); + originatorPartner->add_parameter_group_number_callback(PARAMETER_GROUP_NUMBER, check_can_message, nullptr, recipientECU); + // Send a classic CAN message to a specific destination(8 bytes or less) - if (running && isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, ETPTestBuffer, isobus::CAN_DATA_LENGTH, TestInternalECU, TestPartner)) + if (running && CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), CAN_DATA_LENGTH, originatorECU, recipientPartner)) { std::cout << "Sent a normal CAN Message with length 8" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(4)); // Arbitrary } // Send a classic CAN message to global (0xFF) (8 bytes or less) - if (running && isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, ETPTestBuffer, isobus::CAN_DATA_LENGTH, TestInternalECU)) + if (running && CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), CAN_DATA_LENGTH, originatorECU)) { std::cout << "Sent a broadcast CAN Message with length 8" << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(4)); // Arbitrary } - // CM Tx Example - // This loop sends all possible TP CM message sizes. - // This will take a long time - for (std::uint32_t i = 9; i <= MAX_TP_SIZE_BYTES; i++) + // Send (Extended) Transport Protocol destination-destination specific messages of exponentially increasing size + // This will take a while to complete + std::uint32_t message_length = 9; // Arbitrary starting point + while (running && (message_length <= MAX_MESSAGE_SIZE_BYTES) && (message_length <= MAX_ETP_MESSAGE_SIZE_BYTES)) { - if (!running) + if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU, recipientPartner)) { - break; + std::cout << std::endl; // End the progress bar + std::cout << "Sending a Transport Protocol Message with length " << message_length << std::endl; + message_length *= 2; } + else + { + print_progress_bar(CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front()); + } + std::this_thread::sleep_for(std::chrono::milliseconds(4)); + } - // Send message - if (isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, TPTestBuffer, i, TestInternalECU, TestPartner)) + // Send Broadcast Transport Protocol messages (BAM) of exponentially increasing size + // This will take a while to complete + message_length = 11; // Arbitrary starting point + while (running && (message_length <= MAX_MESSAGE_SIZE_BYTES) && (message_length <= MAX_TP_MESSAGE_SIZE_BYTES)) + { + if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU)) { - std::cout << "Started TP CM Session with length " << i << std::endl; + std::cout << std::endl; // End the progress bar + std::cout << "Sending a Broadcast Transport Protocol Message with length " << message_length << std::endl; + message_length *= 2; } else { - std::cout << "Failed starting TP CM Session with length " << i << std::endl; + print_progress_bar(CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front()); } - // Wait for this session to complete before starting the next - // This sleep value is arbitrary - std::this_thread::sleep_for(std::chrono::milliseconds(i * 2)); + std::this_thread::sleep_for(std::chrono::milliseconds(4)); } - // BAM Tx Exmaple - // This loop sends all possible BAM message sizes - // This will take a very long time - for (std::uint32_t i = 9; i <= MAX_TP_SIZE_BYTES; i++) + CANHardwareInterface::stop(); + return 0; +} + +void check_can_message(const CANMessage &message, void *) +{ + for (std::uint32_t i = 0; i < message.get_data_length(); i++) { - if (!running) + if (message.get_data()[i] != (i % 0xFF)) { - break; + std::cout << std::endl // End the progress bar + << "Received CAN with incorrect data!!!" << std::endl; + return; } + } +} + +void print_progress_bar(const TransportProtocolSessionBase *session) +{ + constexpr std::uint8_t width = 50; + float percentage = session->get_percentage_bytes_transferred(); - // Send message - if (isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, TPTestBuffer, i, TestInternalECU)) + std::cout << "["; + for (std::uint8_t i = 0; i < width; i++) + { + if (i < static_cast(percentage * width)) + { + std::cout << "="; + } + else if (i == static_cast(percentage * width)) { - std::cout << "Started BAM Session with length " << i << std::endl; + std::cout << ">"; } else { - std::cout << "Failed starting BAM Session with length " << i << std::endl; + std::cout << " "; } - // Wait for this session to complete before starting the next, or it will fail as only 1 BAM session is possible at a time - std::this_thread::sleep_for(std::chrono::milliseconds(2 * (isobus::CANNetworkManager::CANNetwork.get_configuration().get_minimum_time_between_transport_protocol_bam_frames() * ((i + 1) / 7)))); } - - // ETP Example - // Send one ETP message - if (running && isobus::CANNetworkManager::CANNetwork.send_can_message(0xEF00, ETPTestBuffer, ETP_TEST_SIZE, TestInternalECU, TestPartner)) - { - std::cout << "Started ETP Session with length " << ETP_TEST_SIZE << std::endl; - std::this_thread::sleep_for(std::chrono::milliseconds(2000)); - } - - isobus::CANHardwareInterface::stop(); - - return 0; + std::cout << "] " << static_cast(percentage * 100.0f) << "% (" << session->get_total_bytes_transferred() << "/" << session->get_message_length() << " bytes)\r"; + std::cout.flush(); } diff --git a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp index 8ba8fd9c..430cf447 100644 --- a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp @@ -94,7 +94,7 @@ namespace isobus std::uint32_t get_total_bytes_transferred() const override; /// @brief Get the percentage of bytes that have been sent or received in this session - /// @return The percentage of bytes that have been sent or received + /// @return The percentage of bytes that have been sent or received (between 0 and 1) float get_percentage_bytes_transferred() const override; protected: @@ -134,8 +134,12 @@ namespace isobus /// @return The last packet number that was processed std::uint32_t get_last_packet_number() const; - /// @brief Set the last sequence number that will be processed - /// @param[in] value The last sequence number that will be processed + /// @brief Set the last packet number that was processed + /// @param[in] value The last packet number that was processed + void set_sequence_number_offset(std::uint32_t value); + + /// @brief Set the last sequence number that has be processed + /// @param[in] value The last sequence number that has be processed void set_last_sequency_number(std::uint8_t value); /// @brief Set the last acknowledged packet number by the receiver @@ -195,6 +199,11 @@ namespace isobus /// @returns true if a matching session was found, false if not bool has_session(std::shared_ptr source, std::shared_ptr destination); + /// @brief Gets all the active transport protocol sessions that are currently active + /// @note The list returns pointers to the transport protocol sessions, but they can disappear at any time + /// @returns A list of all the active transport protocol sessions + std::vector get_sessions() const; + /// @brief A generic way for a protocol to process a received message /// @param[in] message A received CAN message void process_message(const CANMessage &message); diff --git a/isobus/include/isobus/isobus/can_network_manager.hpp b/isobus/include/isobus/isobus/can_network_manager.hpp index 7c37226d..71b98a40 100644 --- a/isobus/include/isobus/isobus/can_network_manager.hpp +++ b/isobus/include/isobus/isobus/can_network_manager.hpp @@ -182,6 +182,12 @@ namespace isobus /// @returns A list of all the control functions std::list> get_control_functions(bool includingOffline) const; + /// @brief Gets all the active transport protocol sessions that are currently active + /// @note The list returns pointers to the transport protocol sessions, but they can disappear at any time + /// @param[in] canPortIndex The CAN channel index to get the transport protocol sessions for + /// @returns A list of all the active transport protocol sessions + std::vector get_active_transport_protocol_sessions(std::uint8_t canPortIndex) const; + /// @brief Returns the class instance of the NMEA2k fast packet protocol. /// Use this to register for FP multipacket messages /// @returns The class instance of the NMEA2k fast packet protocol. @@ -371,8 +377,8 @@ namespace isobus static constexpr std::uint32_t BUSLOAD_UPDATE_FREQUENCY_MS = 100; ///< Bus load bit accumulation happens over a 100ms window CANNetworkConfiguration configuration; ///< The configuration for this network manager - TransportProtocolManager transportProtocol; ///< Instance of the transport protocol manager - ExtendedTransportProtocolManager extendedTransportProtocol; ///< Instance of the extended transport protocol manager + std::array, CAN_PORT_MAXIMUM> transportProtocols; ///< One instance of the transport protocol manager for each channel + std::array, CAN_PORT_MAXIMUM> extendedTransportProtocols; ///< One instance of the extended transport protocol manager for each channel FastPacketProtocol fastPacketProtocol; ///< Instance of the fast packet protocol std::array, CAN_PORT_MAXIMUM> busloadMessageBitsHistory; ///< Stores the approximate number of bits processed on each channel over multiple previous time windows diff --git a/isobus/include/isobus/isobus/can_transport_protocol.hpp b/isobus/include/isobus/isobus/can_transport_protocol.hpp index 6b7c19fa..7ff4e01c 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol.hpp @@ -101,7 +101,7 @@ namespace isobus std::uint32_t get_total_bytes_transferred() const override; /// @brief Get the percentage of bytes that have been sent or received in this session - /// @return The percentage of bytes that have been sent or received + /// @return The percentage of bytes that have been sent or received (between 0 and 1) float get_percentage_bytes_transferred() const override; protected: @@ -211,6 +211,11 @@ namespace isobus /// @returns true if a matching session was found, false if not bool has_session(std::shared_ptr source, std::shared_ptr destination); + /// @brief Gets all the active transport protocol sessions that are currently active + /// @note The list returns pointers to the transport protocol sessions, but they can disappear at any time + /// @returns A list of all the active transport protocol sessions + std::vector get_sessions() const; + /// @brief A generic way for a protocol to process a received message /// @param[in] message A received CAN message void process_message(const CANMessage &message); diff --git a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp index 4bd01401..cf979f03 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp @@ -92,7 +92,7 @@ namespace isobus virtual std::uint32_t get_total_bytes_transferred() const = 0; /// @brief Get the percentage of bytes that have been sent or received in this session - /// @return The percentage of bytes that have been sent or received + /// @return The percentage of bytes that have been sent or received (between 0 and 1) virtual float get_percentage_bytes_transferred() const = 0; /// @brief Get the control function that is sending the message diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index 99a2098a..bedc7d94 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -60,7 +60,12 @@ namespace isobus std::uint32_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_total_bytes_transferred() const { - return get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + uint32_t transferred = get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + if (transferred > get_message_length()) + { + transferred = get_message_length(); + } + return transferred; } float ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_percentage_bytes_transferred() const @@ -106,6 +111,12 @@ namespace isobus return lastSequenceNumber + sequenceNumberOffset; } + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_sequence_number_offset(std::uint32_t value) + { + sequenceNumberOffset = value; + update_timestamp(); + } + void ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::set_last_sequency_number(std::uint8_t value) { lastSequenceNumber = value; @@ -186,6 +197,8 @@ namespace isobus newSession.set_state(StateMachineState::SendClearToSend); activeSessions.push_back(std::move(newSession)); + CANStackLogger::debug("[ETP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); + update_state_machine(activeSessions.back()); } } @@ -225,6 +238,7 @@ namespace isobus if (0 != packetsToBeSent) { session->set_state(StateMachineState::SendDataPacketOffset); + update_state_machine(*session); } } } @@ -241,7 +255,7 @@ namespace isobus std::uint8_t numberOfPackets, std::uint32_t packetOffset) { - auto session = get_session(destination, source); + auto session = get_session(source, destination); if (nullptr != session) { if (session->get_parameter_group_number() != parameterGroupNumber) @@ -250,18 +264,18 @@ namespace isobus abort_session(*session, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); } - else if (StateMachineState::WaitForClearToSend != session->state) + else if (StateMachineState::WaitForDataPacketOffset != session->state) { // The session exists, but we're not in the right state to receive a DPO, so we must abort CANStackLogger::warn("[ETP]: Received a Data Packet Offset message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); abort_session(*session, ConnectionAbortReason::UnexpectedDataPacketOffsetReceived); } - else if (packetOffset > session->get_cts_number_of_packet_limit()) + else if (numberOfPackets > session->get_cts_number_of_packet_limit()) { - CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with a different number of packets than our CTS, aborting...", parameterGroupNumber); + CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with an higher number of packets than our CTS, aborting...", parameterGroupNumber); abort_session(*session, ConnectionAbortReason::DataPacketOffsetExceedsClearToSend); } - else if (packetOffset != (session->get_last_acknowledged_packet_number() - 1)) + else if (packetOffset != session->get_last_acknowledged_packet_number()) { CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); abort_session(*session, ConnectionAbortReason::BadDataPacketOffset); @@ -269,6 +283,8 @@ namespace isobus else { session->set_dpo_number_of_packets(numberOfPackets); + session->set_sequence_number_offset(packetOffset); + session->set_last_sequency_number(0); // If 0 was sent as the packet number, they want us to wait. // Just sit here in this state until we get a non-zero packet count @@ -295,10 +311,10 @@ namespace isobus { if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) { - CANStackLogger::debug("[ETP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); session->state = StateMachineState::None; bool successful = (numberOfBytesTransferred == session->get_message_length()); close_session(*session, successful); + CANStackLogger::debug("[ETP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); } else { @@ -479,10 +495,12 @@ namespace isobus canMessageReceivedCallback(completedMessage); close_session(*session, true); + CANStackLogger::debug("[ETP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); } else if (session->get_dpo_number_of_packets_remaining() == 0) { - send_clear_to_send(*session); + session->set_state(StateMachineState::SendClearToSend); + update_state_machine(*session); } } else @@ -526,13 +544,16 @@ namespace isobus void *parentPointer) { // Return false early if we can't send the message - if ((nullptr == data) || (data->size() <= CAN_DATA_LENGTH) || (data->size() > MAX_PROTOCOL_DATA_LENGTH)) + if ((nullptr == data) || (data->size() <= 1785) || (data->size() > MAX_PROTOCOL_DATA_LENGTH)) { // Invalid message length return false; } - else if ((nullptr == source) || (!source->get_address_valid()) || has_session(source, destination)) + else if ((nullptr == source) || (!source->get_address_valid()) || + (nullptr == destination) || (!destination->get_address_valid()) || + has_session(source, destination)) { + // Invalid source/destination or already have a session for this source and destination return false; } @@ -556,6 +577,7 @@ namespace isobus destination->get_address()); activeSessions.push_back(std::move(session)); + update_state_machine(session); return true; } @@ -655,8 +677,8 @@ namespace isobus { if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout rx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); - if (session.get_dpo_number_of_packets() > 0) + CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); + if (session.get_cts_number_of_packet_limit() > 0) { // A connection is only considered established if we've received at least one CTS before // And we can only abort a connection if it's considered established @@ -683,7 +705,7 @@ namespace isobus { if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected DPO)", session.get_parameter_group_number()); + CANStackLogger::error("[ETP]: Timeout rx session for 0x%05X (expected DPO)", session.get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -694,6 +716,7 @@ namespace isobus if (send_data_packet_offset(session)) { session.set_state(StateMachineState::SendDataTransferPackets); + update_state_machine(session); // Immediately update the state machine to send the next message } } break; @@ -708,7 +731,7 @@ namespace isobus { if (session.get_time_since_last_update() > T1_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); + CANStackLogger::error("[ETP]: Timeout for destination-specific rx session (expected sequencial data frame)"); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -718,7 +741,7 @@ namespace isobus { if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); + CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -785,128 +808,111 @@ namespace isobus bool ExtendedTransportProtocolManager::send_request_to_send(const ExtendedTransportProtocolSession &session) const { - bool retVal = false; - if (auto source = session.get_source()) - { - const std::array buffer{ - REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - static_cast((session.get_message_length() >> 16) & 0xFF), - static_cast((session.get_message_length() >> 24) & 0xFF), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(source), - session.get_destination(), - CANIdentifier::CANPriority::PriorityLowest7); - } - return retVal; + const std::array buffer{ + REQUEST_TO_SEND_MULTIPLEXOR, + static_cast(session.get_message_length() & 0xFF), + static_cast((session.get_message_length() >> 8) & 0xFF), + static_cast((session.get_message_length() >> 16) & 0xFF), + static_cast((session.get_message_length() >> 24) & 0xFF), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_source()), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7); } bool ExtendedTransportProtocolManager::send_clear_to_send(ExtendedTransportProtocolSession &session) const { - bool retVal = false; - // Since we're the receiving side, we are the destination of the session - if (auto ourControlFunction = session.get_destination()) - { - std::uint32_t nextPacketNumber = session.get_last_packet_number() + 1; - const std::array buffer{ - CLEAR_TO_SEND_MULTIPLEXOR, - session.get_cts_number_of_packet_limit(), - static_cast(nextPacketNumber & 0xFF), - static_cast((nextPacketNumber >> 8) & 0xFF), - static_cast((nextPacketNumber >> 16) & 0xFF), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(ourControlFunction), - session.get_source(), - CANIdentifier::CANPriority::PriorityLowest7); - if (retVal) - { - session.set_acknowledged_packet_number(session.get_last_packet_number()); - } + std::uint32_t nextPacketNumber = session.get_last_packet_number() + 1; + const std::array buffer{ + CLEAR_TO_SEND_MULTIPLEXOR, + session.get_cts_number_of_packet_limit(), + static_cast(nextPacketNumber & 0xFF), + static_cast((nextPacketNumber >> 8) & 0xFF), + static_cast((nextPacketNumber >> 16) & 0xFF), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + bool retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); + if (retVal) + { + session.set_acknowledged_packet_number(session.get_last_packet_number()); } return retVal; } bool isobus::ExtendedTransportProtocolManager::send_data_packet_offset(ExtendedTransportProtocolSession &session) const { - bool retVal = false; - if (auto source = session.get_source()) + std::uint8_t packetsThisSegment = 0xFF; + if (packetsThisSegment > session.get_number_of_remaining_packets()) { - std::uint8_t packetsThisSegment = session.get_number_of_remaining_packets(); - if (packetsThisSegment > session.get_cts_number_of_packet_limit()) - { - packetsThisSegment = session.get_cts_number_of_packet_limit(); - } - else if (packetsThisSegment > 16) - { - //! @todo apply CTS number of packets recommendation of 16 via a configuration option - packetsThisSegment = 16; - } + packetsThisSegment = static_cast(session.get_number_of_remaining_packets()); + } - const std::array buffer{ - DATA_PACKET_OFFSET_MULTIPLXOR, - packetsThisSegment, - static_cast(session.get_last_packet_number()), - static_cast((session.get_last_packet_number() >> 8) & 0xFF), - static_cast((session.get_last_packet_number() >> 16) & 0xFF), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) - }; - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(source), - session.get_source(), - CANIdentifier::CANPriority::PriorityLowest7); - if (retVal) - { - session.set_dpo_number_of_packets(packetsThisSegment); - } + if (packetsThisSegment > session.get_cts_number_of_packet_limit()) + { + packetsThisSegment = session.get_cts_number_of_packet_limit(); + } + else if (packetsThisSegment > 16) + { + //! @todo apply CTS number of packets recommendation of 16 via a configuration option + packetsThisSegment = 16; + } + + const std::array buffer{ + DATA_PACKET_OFFSET_MULTIPLXOR, + packetsThisSegment, + static_cast(session.get_last_packet_number()), + static_cast((session.get_last_packet_number() >> 8) & 0xFF), + static_cast((session.get_last_packet_number() >> 16) & 0xFF), + static_cast(session.get_parameter_group_number() & 0xFF), + static_cast((session.get_parameter_group_number() >> 8) & 0xFF), + static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + }; + bool retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_source()), + session.get_destination(), + CANIdentifier::CANPriority::PriorityLowest7); + if (retVal) + { + session.set_dpo_number_of_packets(packetsThisSegment); + session.set_sequence_number_offset(session.get_last_packet_number()); + session.set_last_sequency_number(0); } return retVal; } bool ExtendedTransportProtocolManager::send_end_of_session_acknowledgement(const ExtendedTransportProtocolSession &session) const { - bool retVal = false; - // Since we're the receiving side, we are the destination of the session - if (auto ourControlFunction = session.get_destination()) - { - std::uint32_t messageLength = session.get_message_length(); - std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); - - const std::array buffer{ - END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, - static_cast(messageLength & 0xFF), - static_cast((messageLength >> 8) & 0xFF), - static_cast((messageLength >> 16) & 0xFF), - static_cast((messageLength >> 24) & 0xFF), - static_cast(parameterGroupNumber & 0xFF), - static_cast((parameterGroupNumber >> 8) & 0xFF), - static_cast((parameterGroupNumber >> 16) & 0xFF), - }; - - retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), - CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(ourControlFunction), - session.get_source(), - CANIdentifier::CANPriority::PriorityLowest7); - } - else - { - CANStackLogger::warn("[ETP]: Attempted to send EOM to null session"); - } - return retVal; + std::uint32_t messageLength = session.get_message_length(); + std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); + + const std::array buffer{ + END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, + static_cast(messageLength & 0xFF), + static_cast((messageLength >> 8) & 0xFF), + static_cast((messageLength >> 16) & 0xFF), + static_cast((messageLength >> 24) & 0xFF), + static_cast(parameterGroupNumber & 0xFF), + static_cast((parameterGroupNumber >> 8) & 0xFF), + static_cast((parameterGroupNumber >> 16) & 0xFF), + }; + + return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), + CANDataSpan(buffer.data(), buffer.size()), + std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session + session.get_source(), + CANIdentifier::CANPriority::PriorityLowest7); } bool ExtendedTransportProtocolManager::has_session(std::shared_ptr source, std::shared_ptr destination) @@ -925,4 +931,15 @@ namespace isobus // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored return (activeSessions.end() != result) ? &(*result) : nullptr; } + + std::vector ExtendedTransportProtocolManager::get_sessions() const + { + std::vector sessions; + sessions.reserve(activeSessions.size()); + for (auto &session : activeSessions) + { + sessions.push_back(&session); + } + return sessions; + } } diff --git a/isobus/src/can_network_manager.cpp b/isobus/src/can_network_manager.cpp index 0108901d..fb386bdd 100644 --- a/isobus/src/can_network_manager.cpp +++ b/isobus/src/can_network_manager.cpp @@ -139,22 +139,22 @@ namespace isobus { messageData.reset(new CANMessageDataView(dataBuffer, dataLength)); } - if (transportProtocol.protocol_transmit_message(parameterGroupNumber, - messageData, - sourceControlFunction, - destinationControlFunction, - transmitCompleteCallback, - parentPointer)) + if (transportProtocols[sourceControlFunction->get_can_port()]->protocol_transmit_message(parameterGroupNumber, + messageData, + sourceControlFunction, + destinationControlFunction, + transmitCompleteCallback, + parentPointer)) { // Successfully sent via the transport protocol retVal = true; } - else if (extendedTransportProtocol.protocol_transmit_message(parameterGroupNumber, - messageData, - sourceControlFunction, - destinationControlFunction, - transmitCompleteCallback, - parentPointer)) + else if (extendedTransportProtocols[sourceControlFunction->get_can_port()]->protocol_transmit_message(parameterGroupNumber, + messageData, + sourceControlFunction, + destinationControlFunction, + transmitCompleteCallback, + parentPointer)) { // Successfully sent via the extended transport protocol retVal = true; @@ -242,7 +242,11 @@ namespace isobus prune_inactive_control_functions(); // Update transport protocols - transportProtocol.update(); + for (std::uint32_t i = 0; i < CAN_PORT_MAXIMUM; i++) + { + transportProtocols[i]->update(); + extendedTransportProtocols[i]->update(); + } for (std::size_t i = 0; i < CANLibProtocol::get_number_protocols(); i++) { @@ -441,6 +445,21 @@ namespace isobus return retVal; } + std::vector isobus::CANNetworkManager::get_active_transport_protocol_sessions(std::uint8_t canPortIndex) const + { + std::vector retVal; + + for (auto currentSession : transportProtocols[canPortIndex]->get_sessions()) + { + retVal.push_back(currentSession); + } + for (auto currentSession : extendedTransportProtocols[canPortIndex]->get_sessions()) + { + retVal.push_back(currentSession); + } + return retVal; + } + FastPacketProtocol &CANNetworkManager::get_fast_packet_protocol() { return fastPacketProtocol; @@ -492,25 +511,32 @@ namespace isobus return retVal; } - CANNetworkManager::CANNetworkManager() : - transportProtocol([this](std::uint32_t parameterGroupNumber, - CANDataSpan data, - std::shared_ptr sourceControlFunction, - std::shared_ptr destinationControlFunction, - CANIdentifier::CANPriority priority) { return this->send_can_message(parameterGroupNumber, data.begin(), data.size(), sourceControlFunction, destinationControlFunction, priority); }, - [this](const CANMessage &message) { this->protocol_message_callback(message); }, - &configuration), - extendedTransportProtocol([this](std::uint32_t parameterGroupNumber, - CANDataSpan data, - std::shared_ptr sourceControlFunction, - std::shared_ptr destinationControlFunction, - CANIdentifier::CANPriority priority) { return this->send_can_message(parameterGroupNumber, data.begin(), data.size(), sourceControlFunction, destinationControlFunction, priority); }, - [this](const CANMessage &message) { this->protocol_message_callback(message); }, - &configuration) + CANNetworkManager::CANNetworkManager() { currentBusloadBitAccumulator.fill(0); lastAddressClaimRequestTimestamp_ms.fill(0); controlFunctionTable.fill({ nullptr }); + + auto send_frame_callback = [this](std::uint32_t parameterGroupNumber, + CANDataSpan data, + std::shared_ptr sourceControlFunction, + std::shared_ptr destinationControlFunction, + CANIdentifier::CANPriority priority) { return this->send_can_message(parameterGroupNumber, data.begin(), data.size(), sourceControlFunction, destinationControlFunction, priority); }; + + for (std::uint8_t i = 0; i < CAN_PORT_MAXIMUM; i++) + { + auto receive_message_callback = [this, i](const CANMessage &message) { + // TODO: hack port_index for now, once network manager isn't a singleton, this can be removed + CANMessage tempMessage(i); + tempMessage.set_identifier(message.get_identifier()); + tempMessage.set_source_control_function(message.get_source_control_function()); + tempMessage.set_destination_control_function(message.get_destination_control_function()); + tempMessage.set_data(message.get_data().data(), static_cast(message.get_data_length())); + this->protocol_message_callback(tempMessage); + }; + transportProtocols[i].reset(new TransportProtocolManager(send_frame_callback, receive_message_callback, &configuration)); + extendedTransportProtocols[i].reset(new ExtendedTransportProtocolManager(send_frame_callback, receive_message_callback, &configuration)); + } } void CANNetworkManager::update_address_table(const CANMessage &message) @@ -1038,7 +1064,8 @@ namespace isobus process_can_message_for_address_violations(currentMessage); // Update Special Callbacks, like protocols and non-cf specific ones - transportProtocol.process_message(currentMessage); + transportProtocols[currentMessage.get_can_port_index()]->process_message(currentMessage); + extendedTransportProtocols[currentMessage.get_can_port_index()]->process_message(currentMessage); process_protocol_pgn_callbacks(currentMessage); process_any_control_function_pgn_callbacks(currentMessage); diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index 51997050..660349b5 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -70,7 +70,12 @@ namespace isobus std::uint32_t TransportProtocolManager::TransportProtocolSession::get_total_bytes_transferred() const { - return get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + uint32_t transferred = get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + if (transferred > get_message_length()) + { + transferred = get_message_length(); + } + return transferred; } float TransportProtocolManager::TransportProtocolSession::get_percentage_bytes_transferred() const @@ -194,7 +199,7 @@ namespace isobus { newSession.set_state(StateMachineState::WaitForDataTransferPacket); activeSessions.push_back(std::move(newSession)); - + update_state_machine(activeSessions.back()); CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); } } @@ -257,6 +262,8 @@ namespace isobus { newSession.set_state(StateMachineState::SendClearToSend); activeSessions.push_back(std::move(newSession)); + CANStackLogger::debug("[TP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); + update_state_machine(activeSessions.back()); } } } @@ -297,6 +304,7 @@ namespace isobus if (0 != packetsToBeSent) { session->set_state(StateMachineState::SendDataTransferPackets); + update_state_machine(*session); } } } @@ -316,9 +324,9 @@ namespace isobus { if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) { - CANStackLogger::debug("[TP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); session->state = StateMachineState::None; close_session(*session, true); + CANStackLogger::debug("[TP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); } else { @@ -524,10 +532,6 @@ namespace isobus { send_end_of_session_acknowledgement(*session); } - else - { - CANStackLogger::debug("[TP]: Completed broadcast rx session for 0x%05X", session->get_parameter_group_number()); - } // Construct the completed message CANMessage completedMessage(0); @@ -542,6 +546,7 @@ namespace isobus canMessageReceivedCallback(completedMessage); close_session(*session, true); + CANStackLogger::debug("[TP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); } else if (session->get_cts_number_of_packets_remaining() == 0) { @@ -636,6 +641,7 @@ namespace isobus destination->get_address()); } activeSessions.push_back(std::move(session)); + update_state_machine(activeSessions.back()); return true; } @@ -756,7 +762,7 @@ namespace isobus { if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout rx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); if (session.get_cts_number_of_packets() > 0) { // A connection is only considered established if we've received at least one CTS before @@ -1011,4 +1017,15 @@ namespace isobus // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored return (activeSessions.end() != result) ? &(*result) : nullptr; } + + std::vector TransportProtocolManager::get_sessions() const + { + std::vector sessions; + sessions.reserve(activeSessions.size()); + for (auto &session : activeSessions) + { + sessions.push_back(&session); + } + return sessions; + } } diff --git a/test/transport_protocol_tests.cpp b/test/transport_protocol_tests.cpp index 416b0c09..5dd469c0 100644 --- a/test/transport_protocol_tests.cpp +++ b/test/transport_protocol_tests.cpp @@ -686,7 +686,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMessageReceiving) std::shared_ptr destinationControlFunction, CANIdentifier::CANPriority priority) { EXPECT_EQ(data.size(), 8); - EXPECT_EQ(sourceControlFunction, receiver); // Since it's a response, the receiver should be the destination + EXPECT_EQ(sourceControlFunction, receiver); // Since it's a response, the receiver should be the source EXPECT_EQ(destinationControlFunction, originator); // Since it's a response, the originator should be the destination EXPECT_EQ(priority, CANIdentifier::CANPriority::PriorityLowest7); From 15c627482a4b29e522a2996e575d56f6b96b05fa Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Wed, 20 Dec 2023 21:03:28 +0100 Subject: [PATCH 5/9] refactor(tp/etp): switch to shared_ptr's for the sessions in memory --- examples/transport_layer/main.cpp | 41 ++- .../can_extended_transport_protocol.hpp | 41 +-- .../isobus/isobus/can_network_manager.hpp | 2 +- .../isobus/isobus/can_transport_protocol.hpp | 78 ++-- .../isobus/can_transport_protocol_base.hpp | 2 +- .../src/can_extended_transport_protocol.cpp | 296 +++++++--------- isobus/src/can_network_manager.cpp | 15 +- isobus/src/can_transport_protocol.cpp | 334 ++++++++---------- isobus/src/can_transport_protocol_base.cpp | 5 + 9 files changed, 363 insertions(+), 451 deletions(-) diff --git a/examples/transport_layer/main.cpp b/examples/transport_layer/main.cpp index 4f6e9575..c5d2d912 100644 --- a/examples/transport_layer/main.cpp +++ b/examples/transport_layer/main.cpp @@ -25,7 +25,7 @@ using namespace isobus; // Forward declarations void check_can_message(const CANMessage &message, void *); -void print_progress_bar(const TransportProtocolSessionBase *session); +void print_progress_bar(const std::shared_ptr session); // The example sends a series of CAN messages of incrementing length to itself and checks that the data is correct int main() @@ -121,17 +121,26 @@ int main() // Send (Extended) Transport Protocol destination-destination specific messages of exponentially increasing size // This will take a while to complete std::uint32_t message_length = 9; // Arbitrary starting point + std::shared_ptr session = nullptr; while (running && (message_length <= MAX_MESSAGE_SIZE_BYTES) && (message_length <= MAX_ETP_MESSAGE_SIZE_BYTES)) { - if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU, recipientPartner)) + if (session == nullptr) { - std::cout << std::endl; // End the progress bar - std::cout << "Sending a Transport Protocol Message with length " << message_length << std::endl; - message_length *= 2; + if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU, recipientPartner)) + { + std::cout << "Sending a Transport Protocol Message with length " << message_length << std::endl; + message_length *= 2; + session = CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front(); + } } else { - print_progress_bar(CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front()); + print_progress_bar(session); + if (session.use_count() == 1) // We are the only ones holding a reference to the session, so it must be done/failed + { + std::cout << std::endl; // End the progress bar + session = nullptr; + } } std::this_thread::sleep_for(std::chrono::milliseconds(4)); } @@ -141,15 +150,23 @@ int main() message_length = 11; // Arbitrary starting point while (running && (message_length <= MAX_MESSAGE_SIZE_BYTES) && (message_length <= MAX_TP_MESSAGE_SIZE_BYTES)) { - if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU)) + if (session == nullptr) { - std::cout << std::endl; // End the progress bar - std::cout << "Sending a Broadcast Transport Protocol Message with length " << message_length << std::endl; - message_length *= 2; + if (CANNetworkManager::CANNetwork.send_can_message(PARAMETER_GROUP_NUMBER, sendBuffer.data(), message_length, originatorECU)) + { + std::cout << "Sending a Broadcast Transport Protocol Message with length " << message_length << std::endl; + message_length *= 2; + session = CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front(); + } } else { - print_progress_bar(CANNetworkManager::CANNetwork.get_active_transport_protocol_sessions(0).front()); + print_progress_bar(session); + if (session.use_count() == 1) // We are the only ones holding a reference to the session, so it must be done/failed + { + std::cout << std::endl; // End the progress bar + session = nullptr; + } } std::this_thread::sleep_for(std::chrono::milliseconds(4)); } @@ -171,7 +188,7 @@ void check_can_message(const CANMessage &message, void *) } } -void print_progress_bar(const TransportProtocolSessionBase *session) +void print_progress_bar(const std::shared_ptr session) { constexpr std::uint8_t width = 50; float percentage = session->get_percentage_bytes_transferred(); diff --git a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp index 430cf447..ca052bc2 100644 --- a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp @@ -72,18 +72,7 @@ namespace isobus class ExtendedTransportProtocolSession : public TransportProtocolSessionBase { public: - ~ExtendedTransportProtocolSession() override = default; ///< Default destructor - ExtendedTransportProtocolSession(const ExtendedTransportProtocolSession &obj) = delete; ///< No copy constructor - ExtendedTransportProtocolSession &operator=(const ExtendedTransportProtocolSession &obj) = delete; ///< No copy assignment operator - - /// @brief The move constructor for a session - /// @param[in] obj The session to move - ExtendedTransportProtocolSession(ExtendedTransportProtocolSession &&obj) noexcept; - - /// @brief The move assignment operator for a session - /// @param[in] obj The session to move - /// @returns A reference to the moved session - ExtendedTransportProtocolSession &operator=(ExtendedTransportProtocolSession &&obj) noexcept; + using TransportProtocolSessionBase::TransportProtocolSessionBase; ///< Inherit the base constructor /// @brief Get the state of the session /// @return The state of the session @@ -93,15 +82,9 @@ namespace isobus /// @return The number of bytes that have been sent or received std::uint32_t get_total_bytes_transferred() const override; - /// @brief Get the percentage of bytes that have been sent or received in this session - /// @return The percentage of bytes that have been sent or received (between 0 and 1) - float get_percentage_bytes_transferred() const override; - protected: friend class ExtendedTransportProtocolManager; ///< Allows the ETP manager full access - using TransportProtocolSessionBase::TransportProtocolSessionBase; ///< Inherit the base constructor - /// @brief Set the state of the session /// @param[in] value The state to set the session to void set_state(StateMachineState value); @@ -202,7 +185,7 @@ namespace isobus /// @brief Gets all the active transport protocol sessions that are currently active /// @note The list returns pointers to the transport protocol sessions, but they can disappear at any time /// @returns A list of all the active transport protocol sessions - std::vector get_sessions() const; + const std::vector> &get_sessions() const; /// @brief A generic way for a protocol to process a received message /// @param[in] message A received CAN message @@ -228,7 +211,7 @@ namespace isobus /// @param[in] session The session to abort /// @param[in] reason The reason we're aborting the session /// @returns true if the abort was send OK, false if not sent - bool abort_session(const ExtendedTransportProtocolSession &session, ConnectionAbortReason reason); + bool abort_session(std::shared_ptr &session, ConnectionAbortReason reason); /// @brief Send an abort with no corresponding session with the specified abort reason. Sends a CAN message. /// @param[in] sender The sender of the abort @@ -244,31 +227,31 @@ namespace isobus /// @brief Gracefully closes a session to prepare for a new session /// @param[in] session The session to close /// @param[in] successful Denotes if the session was successful - void close_session(const ExtendedTransportProtocolSession &session, bool successful); + void close_session(std::shared_ptr &session, bool successful); /// @brief Sends the "request to send" message as part of initiating a transmit /// @param[in] session The session for which we're sending the RTS /// @returns true if the RTS was sent, false if sending was not successful - bool send_request_to_send(const ExtendedTransportProtocolSession &session) const; + bool send_request_to_send(std::shared_ptr &session) const; /// @brief Sends the "clear to send" message /// @param[in] session The session for which we're sending the CTS /// @returns true if the CTS was sent, false if sending was not successful - bool send_clear_to_send(ExtendedTransportProtocolSession &session) const; + bool send_clear_to_send(std::shared_ptr &session) const; /// @brief Sends the "data packet offset" message for the provided session /// @param[in] session The session for which we're sending the DPO /// @returns true if the DPO was sent, false if sending was not successful - bool send_data_packet_offset(ExtendedTransportProtocolSession &session) const; + bool send_data_packet_offset(std::shared_ptr &session) const; /// @brief Sends the "end of message acknowledgement" message for the provided session /// @param[in] session The session for which we're sending the EOM ACK /// @returns true if the EOM was sent, false if sending was not successful - bool send_end_of_session_acknowledgement(const ExtendedTransportProtocolSession &session) const; + bool send_end_of_session_acknowledgement(std::shared_ptr &session) const; ///@brief Sends data transfer packets for the specified ExtendedTransportProtocolSession. /// @param[in] session The ExtendedTransportProtocolSession for which to send data transfer packets. - void send_data_transfer_packets(ExtendedTransportProtocolSession &session) const; + void send_data_transfer_packets(std::shared_ptr &session) const; /// @brief Processes a request to send a message over the CAN transport protocol. /// @param[in] source The shared pointer to the source control function. @@ -319,13 +302,13 @@ namespace isobus /// @param[in] source The source control function for the session /// @param[in] destination The destination control function for the session /// @returns a matching session, or nullptr if no session matched the supplied parameters - ExtendedTransportProtocolSession *get_session(std::shared_ptr source, std::shared_ptr destination); + std::shared_ptr get_session(std::shared_ptr source, std::shared_ptr destination); /// @brief Update the state machine for the passed in session /// @param[in] session The session to update - void update_state_machine(ExtendedTransportProtocolSession &session); + void update_state_machine(std::shared_ptr &session); - std::vector activeSessions; ///< A list of all active ETP sessions + std::vector> activeSessions; ///< A list of all active ETP sessions const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame const CANMessageCallback canMessageReceivedCallback; ///< A callback for when a complete CAN message is received using the ETP protocol const CANNetworkConfiguration *configuration; ///< The configuration to use for this protocol diff --git a/isobus/include/isobus/isobus/can_network_manager.hpp b/isobus/include/isobus/isobus/can_network_manager.hpp index 71b98a40..ce574d36 100644 --- a/isobus/include/isobus/isobus/can_network_manager.hpp +++ b/isobus/include/isobus/isobus/can_network_manager.hpp @@ -186,7 +186,7 @@ namespace isobus /// @note The list returns pointers to the transport protocol sessions, but they can disappear at any time /// @param[in] canPortIndex The CAN channel index to get the transport protocol sessions for /// @returns A list of all the active transport protocol sessions - std::vector get_active_transport_protocol_sessions(std::uint8_t canPortIndex) const; + std::list> get_active_transport_protocol_sessions(std::uint8_t canPortIndex) const; /// @brief Returns the class instance of the NMEA2k fast packet protocol. /// Use this to register for FP multipacket messages diff --git a/isobus/include/isobus/isobus/can_transport_protocol.hpp b/isobus/include/isobus/isobus/can_transport_protocol.hpp index 7ff4e01c..c51517c7 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol.hpp @@ -71,18 +71,26 @@ namespace isobus class TransportProtocolSession : public TransportProtocolSessionBase { public: - ~TransportProtocolSession() override = default; ///< Default destructor - TransportProtocolSession(const TransportProtocolSession &obj) = delete; ///< No copy constructor - TransportProtocolSession &operator=(const TransportProtocolSession &obj) = delete; ///< No copy assignment operator - - /// @brief The move constructor for a session - /// @param[in] obj The session to move - TransportProtocolSession(TransportProtocolSession &&obj) noexcept; - - /// @brief The move assignment operator for a session - /// @param[in] obj The session to move - /// @returns A reference to the moved session - TransportProtocolSession &operator=(TransportProtocolSession &&obj) noexcept; + /// @brief The constructor for a session, for advanced use only. + /// In most cases, you should use the CANNetworkManager::send_can_message() function to transmit messages. + /// @param[in] direction The direction of the session + /// @param[in] data Data buffer (will be moved into the session) + /// @param[in] parameterGroupNumber The PGN of the message + /// @param[in] totalMessageSize The total size of the message in bytes + /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message + /// @param[in] source The source control function + /// @param[in] destination The destination control function + /// @param[in] sessionCompleteCallback A callback for when the session completes + /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks + TransportProtocolSession(TransportProtocolSessionBase::Direction direction, + std::unique_ptr data, + std::uint32_t parameterGroupNumber, + std::uint16_t totalMessageSize, + std::uint8_t clearToSendPacketMax, + std::shared_ptr source, + std::shared_ptr destination, + TransmitCompleteCallback sessionCompleteCallback, + void *parentPointer); /// @brief Get the state of the session /// @return The state of the session @@ -100,33 +108,9 @@ namespace isobus /// @return The number of bytes that have been sent or received std::uint32_t get_total_bytes_transferred() const override; - /// @brief Get the percentage of bytes that have been sent or received in this session - /// @return The percentage of bytes that have been sent or received (between 0 and 1) - float get_percentage_bytes_transferred() const override; - protected: friend class TransportProtocolManager; ///< Allows the TP manager full access - /// @brief The constructor for a session - /// @param[in] direction The direction of the session - /// @param[in] data Data buffer (will be moved into the session) - /// @param[in] parameterGroupNumber The PGN of the message - /// @param[in] totalMessageSize The total size of the message in bytes - /// @param[in] clearToSendPacketMax The maximum number of packets that can be sent per CTS as indicated by the RTS message - /// @param[in] source The source control function - /// @param[in] destination The destination control function - /// @param[in] sessionCompleteCallback A callback for when the session completes - /// @param[in] parentPointer A generic context object for the tx complete and chunk callbacks - TransportProtocolSession(TransportProtocolSessionBase::Direction direction, - std::unique_ptr data, - std::uint32_t parameterGroupNumber, - std::uint16_t totalMessageSize, - std::uint8_t clearToSendPacketMax, - std::shared_ptr source, - std::shared_ptr destination, - TransmitCompleteCallback sessionCompleteCallback, - void *parentPointer); - /// @brief Set the state of the session /// @param[in] value The state to set the session to void set_state(StateMachineState value); @@ -214,7 +198,7 @@ namespace isobus /// @brief Gets all the active transport protocol sessions that are currently active /// @note The list returns pointers to the transport protocol sessions, but they can disappear at any time /// @returns A list of all the active transport protocol sessions - std::vector get_sessions() const; + const std::vector> &get_sessions() const; /// @brief A generic way for a protocol to process a received message /// @param[in] message A received CAN message @@ -240,7 +224,7 @@ namespace isobus /// @param[in] session The session to abort /// @param[in] reason The reason we're aborting the session /// @returns true if the abort was send OK, false if not sent - bool abort_session(const TransportProtocolSession &session, ConnectionAbortReason reason); + bool abort_session(std::shared_ptr &session, ConnectionAbortReason reason); /// @brief Send an abort with no corresponding session with the specified abort reason. Sends a CAN message. /// @param[in] sender The sender of the abort @@ -256,31 +240,31 @@ namespace isobus /// @brief Gracefully closes a session to prepare for a new session /// @param[in] session The session to close /// @param[in] successful Denotes if the session was successful - void close_session(const TransportProtocolSession &session, bool successful); + void close_session(std::shared_ptr &session, bool successful); /// @brief Sends the "broadcast announce" message /// @param[in] session The session for which we're sending the BAM /// @returns true if the BAM was sent, false if sending was not successful - bool send_broadcast_announce_message(const TransportProtocolSession &session) const; + bool send_broadcast_announce_message(std::shared_ptr &session) const; /// @brief Sends the "request to send" message as part of initiating a transmit /// @param[in] session The session for which we're sending the RTS /// @returns true if the RTS was sent, false if sending was not successful - bool send_request_to_send(const TransportProtocolSession &session) const; + bool send_request_to_send(std::shared_ptr &session) const; /// @brief Sends the "clear to send" message /// @param[in] session The session for which we're sending the CTS /// @returns true if the CTS was sent, false if sending was not successful - bool send_clear_to_send(TransportProtocolSession &session) const; + bool send_clear_to_send(std::shared_ptr &session) const; /// @brief Sends the "end of message acknowledgement" message for the provided session /// @param[in] session The session for which we're sending the EOM ACK /// @returns true if the EOM was sent, false if sending was not successful - bool send_end_of_session_acknowledgement(const TransportProtocolSession &session) const; + bool send_end_of_session_acknowledgement(std::shared_ptr &session) const; ///@brief Sends data transfer packets for the specified TransportProtocolSession. /// @param[in] session The TransportProtocolSession for which to send data transfer packets. - void send_data_transfer_packets(TransportProtocolSession &session); + void send_data_transfer_packets(std::shared_ptr &session); /// @brief Processes a broadcast announce message. /// @param[in] source The source control function that sent the broadcast announce message. @@ -331,13 +315,13 @@ namespace isobus /// @param[in] source The source control function for the session /// @param[in] destination The destination control function for the session /// @returns a matching session, or nullptr if no session matched the supplied parameters - TransportProtocolSession *get_session(std::shared_ptr source, std::shared_ptr destination); + std::shared_ptr get_session(std::shared_ptr source, std::shared_ptr destination); /// @brief Update the state machine for the passed in session /// @param[in] session The session to update - void update_state_machine(TransportProtocolSession &session); + void update_state_machine(std::shared_ptr &session); - std::vector activeSessions; ///< A list of all active TP sessions + std::vector> activeSessions; ///< A list of all active TP sessions const CANMessageFrameCallback sendCANFrameCallback; ///< A callback for sending a CAN frame const CANMessageCallback canMessageReceivedCallback; ///< A callback for when a complete CAN message is received using the TP protocol const CANNetworkConfiguration *configuration; ///< The configuration to use for this protocol diff --git a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp index cf979f03..f2fd693d 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp @@ -93,7 +93,7 @@ namespace isobus /// @brief Get the percentage of bytes that have been sent or received in this session /// @return The percentage of bytes that have been sent or received (between 0 and 1) - virtual float get_percentage_bytes_transferred() const = 0; + float get_percentage_bytes_transferred() const; /// @brief Get the control function that is sending the message /// @return The source control function diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index bedc7d94..3c900ba7 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -22,31 +22,6 @@ namespace isobus { - // Explicitly define the move constructor and assignment operator to ensure that the base class is moved correctly - // See https://stackoverflow.com/a/15351528 for why this is necessary - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::ExtendedTransportProtocolSession(ExtendedTransportProtocolManager::ExtendedTransportProtocolSession &&obj) noexcept : - TransportProtocolSessionBase(std::move(obj)), - state(obj.state), - lastSequenceNumber(obj.lastSequenceNumber), - sequenceNumberOffset(obj.sequenceNumberOffset), - lastAcknowledgedPacketNumber(obj.lastAcknowledgedPacketNumber), - dataPacketOffsetPacketCount(obj.dataPacketOffsetPacketCount), - clearToSendPacketCountLimit(obj.clearToSendPacketCountLimit) - { - } - - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession &ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::operator=(ExtendedTransportProtocolSession &&obj) noexcept - { - TransportProtocolSessionBase::operator=(std::move(obj)); - state = obj.state; - lastSequenceNumber = obj.lastSequenceNumber; - sequenceNumberOffset = obj.sequenceNumberOffset; - lastAcknowledgedPacketNumber = obj.lastAcknowledgedPacketNumber; - dataPacketOffsetPacketCount = obj.dataPacketOffsetPacketCount; - clearToSendPacketCountLimit = obj.clearToSendPacketCountLimit; - return *this; - } - ExtendedTransportProtocolManager::StateMachineState ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_state() const { return state; @@ -68,11 +43,6 @@ namespace isobus return transferred; } - float ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_percentage_bytes_transferred() const - { - return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()); - } - std::uint8_t ExtendedTransportProtocolManager::ExtendedTransportProtocolSession::get_dpo_number_of_packets_remaining() const { auto packetsSinceDPO = static_cast(get_last_packet_number() - lastAcknowledgedPacketNumber); @@ -177,28 +147,28 @@ namespace isobus if (oldSession->get_parameter_group_number() != parameterGroupNumber) { CANStackLogger::error("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", parameterGroupNumber); - abort_session(*oldSession, ConnectionAbortReason::AlreadyInCMSession); + abort_session(oldSession, ConnectionAbortReason::AlreadyInCMSession); } else { CANStackLogger::warn("[ETP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", parameterGroupNumber); - close_session(*oldSession, false); + close_session(oldSession, false); } } - ExtendedTransportProtocolSession newSession(ExtendedTransportProtocolSession::Direction::Receive, - std::unique_ptr(new CANMessageDataVector(totalMessageSize)), - parameterGroupNumber, - totalMessageSize, - source, - destination, - nullptr, // No callback - nullptr); + auto newSession = std::make_shared(ExtendedTransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + source, + destination, + nullptr, // No callback + nullptr); - newSession.set_state(StateMachineState::SendClearToSend); - activeSessions.push_back(std::move(newSession)); + newSession->set_state(StateMachineState::SendClearToSend); + activeSessions.push_back(newSession); CANStackLogger::debug("[ETP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); - update_state_machine(activeSessions.back()); + update_state_machine(newSession); } } @@ -214,19 +184,19 @@ namespace isobus if (session->get_parameter_group_number() != parameterGroupNumber) { CANStackLogger::error("[ETP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else if (nextPacketNumber > session->get_total_number_of_packets()) { CANStackLogger::error("[ETP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::NumberOfClearToSendPacketsExceedsMessage); + abort_session(session, ConnectionAbortReason::NumberOfClearToSendPacketsExceedsMessage); } else if (StateMachineState::WaitForClearToSend != session->state) { // The session exists, but we're not in the right state to receive a CTS, so we must abort - CANStackLogger::warn("[ETP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + CANStackLogger::warn("[ETP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); + abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else { @@ -238,7 +208,7 @@ namespace isobus if (0 != packetsToBeSent) { session->set_state(StateMachineState::SendDataPacketOffset); - update_state_machine(*session); + update_state_machine(session); } } } @@ -261,24 +231,24 @@ namespace isobus if (session->get_parameter_group_number() != parameterGroupNumber) { CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X while a session already existed for this source and destination with a different PGN, sending abort for both...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); + abort_session(session, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::UnexpectedDataPacketOffsetPGN); } else if (StateMachineState::WaitForDataPacketOffset != session->state) { // The session exists, but we're not in the right state to receive a DPO, so we must abort - CANStackLogger::warn("[ETP]: Received a Data Packet Offset message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::UnexpectedDataPacketOffsetReceived); + CANStackLogger::warn("[ETP]: Received a Data Packet Offset message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); + abort_session(session, ConnectionAbortReason::UnexpectedDataPacketOffsetReceived); } else if (numberOfPackets > session->get_cts_number_of_packet_limit()) { CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with an higher number of packets than our CTS, aborting...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::DataPacketOffsetExceedsClearToSend); + abort_session(session, ConnectionAbortReason::DataPacketOffsetExceedsClearToSend); } else if (packetOffset != session->get_last_acknowledged_packet_number()) { CANStackLogger::error("[ETP]: Received a Data Packet Offset message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::BadDataPacketOffset); + abort_session(session, ConnectionAbortReason::BadDataPacketOffset); } else { @@ -313,7 +283,7 @@ namespace isobus { session->state = StateMachineState::None; bool successful = (numberOfBytesTransferred == session->get_message_length()); - close_session(*session, successful); + close_session(session, successful); CANStackLogger::debug("[ETP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); } else @@ -340,14 +310,14 @@ namespace isobus { foundSession = true; CANStackLogger::error("[ETP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); - close_session(*session, false); + close_session(session, false); } session = get_session(destination, source); if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) { foundSession = true; CANStackLogger::error("[ETP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); - close_session(*session, false); + close_session(session, false); } if (!foundSession) @@ -449,12 +419,12 @@ namespace isobus if (StateMachineState::WaitForDataTransferPacket != session->state) { CANStackLogger::warn("[ETP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); - abort_session(*session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); + abort_session(session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); } else if (sequenceNumber == session->get_last_sequence_number()) { CANStackLogger::error("[ETP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); - abort_session(*session, ConnectionAbortReason::DuplicateSequenceNumber); + abort_session(session, ConnectionAbortReason::DuplicateSequenceNumber); } else if (sequenceNumber == (session->get_last_sequence_number() + 1)) { @@ -480,7 +450,7 @@ namespace isobus if (session->get_number_of_remaining_packets() == 0) { // Send End of Message Acknowledgement for sessions with specific destination only - send_end_of_session_acknowledgement(*session); + send_end_of_session_acknowledgement(session); // Construct the completed message CANMessage completedMessage(0); @@ -494,19 +464,19 @@ namespace isobus completedMessage.set_data(data.data().begin(), static_cast(data.size())); canMessageReceivedCallback(completedMessage); - close_session(*session, true); + close_session(session, true); CANStackLogger::debug("[ETP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); } else if (session->get_dpo_number_of_packets_remaining() == 0) { session->set_state(StateMachineState::SendClearToSend); - update_state_machine(*session); + update_state_machine(session); } } else { CANStackLogger::error("[ETP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); - abort_session(*session, ConnectionAbortReason::BadSequenceNumber); + abort_session(session, ConnectionAbortReason::BadSequenceNumber); } } } @@ -562,21 +532,21 @@ namespace isobus data = data->copy_if_not_owned(std::move(data)); auto dataLength = static_cast(data->size()); - ExtendedTransportProtocolSession session(ExtendedTransportProtocolSession::Direction::Transmit, - std::move(data), - parameterGroupNumber, - dataLength, - source, - destination, - sessionCompleteCallback, - parentPointer); - session.set_state(StateMachineState::SendRequestToSend); + auto session = std::make_shared(ExtendedTransportProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + dataLength, + source, + destination, + sessionCompleteCallback, + parentPointer); + session->set_state(StateMachineState::SendRequestToSend); CANStackLogger::debug("[ETP]: New tx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); - activeSessions.push_back(std::move(session)); + activeSessions.push_back(session); update_state_machine(session); return true; } @@ -586,28 +556,28 @@ namespace isobus // We use a fancy for loop here to allow us to remove sessions from the list while iterating for (std::size_t i = activeSessions.size(); i > 0; i--) { - auto &session = activeSessions.at(i - 1); - if (!session.get_source()->get_address_valid()) + auto session = activeSessions.at(i - 1); + if (!session->get_source()->get_address_valid()) { CANStackLogger::warn("[ETP]: Closing active session as the source control function is no longer valid"); close_session(session, false); } - else if (!session.get_destination()->get_address_valid()) + else if (!session->get_destination()->get_address_valid()) { CANStackLogger::warn("[ETP]: Closing active session as the destination control function is no longer valid"); close_session(session, false); } - else if (StateMachineState::None != session.state) + else if (StateMachineState::None != session->state) { update_state_machine(session); } } } - void ExtendedTransportProtocolManager::send_data_transfer_packets(ExtendedTransportProtocolSession &session) const + void ExtendedTransportProtocolManager::send_data_transfer_packets(std::shared_ptr &session) const { std::array buffer; - std::uint8_t framesToSend = session.get_dpo_number_of_packets_remaining(); + std::uint8_t framesToSend = session->get_dpo_number_of_packets_remaining(); if (framesToSend > configuration->get_max_number_of_network_manager_protocol_frames_per_update()) { framesToSend = configuration->get_max_number_of_network_manager_protocol_frames_per_update(); @@ -616,15 +586,15 @@ namespace isobus // Try and send packets for (std::uint8_t i = 0; i < framesToSend; i++) { - buffer[0] = session.get_last_sequence_number() + 1; + buffer[0] = session->get_last_sequence_number() + 1; - std::uint32_t dataOffset = session.get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + std::uint32_t dataOffset = session->get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) { std::uint32_t index = dataOffset + j; - if (index < session.get_message_length()) + if (index < session->get_message_length()) { - buffer[1 + j] = session.get_data().get_byte(index); + buffer[1 + j] = session->get_data().get_byte(index); } else { @@ -634,11 +604,11 @@ namespace isobus if (sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolDataTransfer), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_source()), - session.get_destination(), + std::static_pointer_cast(session->get_source()), + session->get_destination(), CANIdentifier::CANPriority::PriorityLowest7)) { - session.set_last_sequency_number(session.get_last_sequence_number() + 1); + session->set_last_sequency_number(session->get_last_sequence_number() + 1); } else { @@ -647,19 +617,19 @@ namespace isobus } } - if (session.get_number_of_remaining_packets() == 0) + if (session->get_number_of_remaining_packets() == 0) { - session.set_state(StateMachineState::WaitForEndOfMessageAcknowledge); + session->set_state(StateMachineState::WaitForEndOfMessageAcknowledge); } - else if (session.get_dpo_number_of_packets_remaining() == 0) + else if (session->get_dpo_number_of_packets_remaining() == 0) { - session.set_state(StateMachineState::WaitForClearToSend); + session->set_state(StateMachineState::WaitForClearToSend); } } - void ExtendedTransportProtocolManager::update_state_machine(ExtendedTransportProtocolSession &session) + void ExtendedTransportProtocolManager::update_state_machine(std::shared_ptr &session) { - switch (session.state) + switch (session->state) { case StateMachineState::None: break; @@ -668,17 +638,17 @@ namespace isobus { if (send_request_to_send(session)) { - session.set_state(StateMachineState::WaitForClearToSend); + session->set_state(StateMachineState::WaitForClearToSend); } } break; case StateMachineState::WaitForClearToSend: { - if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) + if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); - if (session.get_cts_number_of_packet_limit() > 0) + CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected CTS)", session->get_parameter_group_number()); + if (session->get_cts_number_of_packet_limit() > 0) { // A connection is only considered established if we've received at least one CTS before // And we can only abort a connection if it's considered established @@ -696,16 +666,16 @@ namespace isobus { if (send_clear_to_send(session)) { - session.set_state(StateMachineState::WaitForDataPacketOffset); + session->set_state(StateMachineState::WaitForDataPacketOffset); } } break; case StateMachineState::WaitForDataPacketOffset: { - if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) + if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout rx session for 0x%05X (expected DPO)", session.get_parameter_group_number()); + CANStackLogger::error("[ETP]: Timeout rx session for 0x%05X (expected DPO)", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -715,7 +685,7 @@ namespace isobus { if (send_data_packet_offset(session)) { - session.set_state(StateMachineState::SendDataTransferPackets); + session->set_state(StateMachineState::SendDataTransferPackets); update_state_machine(session); // Immediately update the state machine to send the next message } } @@ -729,7 +699,7 @@ namespace isobus case StateMachineState::WaitForDataTransferPacket: { - if (session.get_time_since_last_update() > T1_TIMEOUT_MS) + if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { CANStackLogger::error("[ETP]: Timeout for destination-specific rx session (expected sequencial data frame)"); abort_session(session, ConnectionAbortReason::Timeout); @@ -739,9 +709,9 @@ namespace isobus case StateMachineState::WaitForEndOfMessageAcknowledge: { - if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) + if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); + CANStackLogger::error("[ETP]: Timeout tx session for 0x%05X (expected EOMA)", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -749,25 +719,25 @@ namespace isobus } } - bool ExtendedTransportProtocolManager::abort_session(const ExtendedTransportProtocolSession &session, ConnectionAbortReason reason) + bool ExtendedTransportProtocolManager::abort_session(std::shared_ptr &session, ConnectionAbortReason reason) { bool retVal = false; std::shared_ptr myControlFunction; std::shared_ptr partnerControlFunction; - if (ExtendedTransportProtocolSession::Direction::Transmit == session.get_direction()) + if (ExtendedTransportProtocolSession::Direction::Transmit == session->get_direction()) { - myControlFunction = std::static_pointer_cast(session.get_source()); - partnerControlFunction = session.get_destination(); + myControlFunction = std::static_pointer_cast(session->get_source()); + partnerControlFunction = session->get_destination(); } else { - myControlFunction = std::static_pointer_cast(session.get_destination()); - partnerControlFunction = session.get_source(); + myControlFunction = std::static_pointer_cast(session->get_destination()); + partnerControlFunction = session->get_source(); } if ((nullptr != myControlFunction) && (nullptr != partnerControlFunction)) { - retVal = send_abort(myControlFunction, partnerControlFunction, session.get_parameter_group_number(), reason); + retVal = send_abort(myControlFunction, partnerControlFunction, session->get_parameter_group_number(), reason); } close_session(session, false); return retVal; @@ -795,9 +765,9 @@ namespace isobus CANIdentifier::CANPriority::PriorityLowest7); } - void ExtendedTransportProtocolManager::close_session(const ExtendedTransportProtocolSession &session, bool successful) + void ExtendedTransportProtocolManager::close_session(std::shared_ptr &session, bool successful) { - session.complete(successful); + session->complete(successful); auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); if (activeSessions.end() != sessionLocation) { @@ -806,61 +776,61 @@ namespace isobus } } - bool ExtendedTransportProtocolManager::send_request_to_send(const ExtendedTransportProtocolSession &session) const + bool ExtendedTransportProtocolManager::send_request_to_send(std::shared_ptr &session) const { const std::array buffer{ REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - static_cast((session.get_message_length() >> 16) & 0xFF), - static_cast((session.get_message_length() >> 24) & 0xFF), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + static_cast(session->get_message_length() & 0xFF), + static_cast((session->get_message_length() >> 8) & 0xFF), + static_cast((session->get_message_length() >> 16) & 0xFF), + static_cast((session->get_message_length() >> 24) & 0xFF), + static_cast(session->get_parameter_group_number() & 0xFF), + static_cast((session->get_parameter_group_number() >> 8) & 0xFF), + static_cast((session->get_parameter_group_number() >> 16) & 0xFF) }; return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_source()), - session.get_destination(), + std::static_pointer_cast(session->get_source()), + session->get_destination(), CANIdentifier::CANPriority::PriorityLowest7); } - bool ExtendedTransportProtocolManager::send_clear_to_send(ExtendedTransportProtocolSession &session) const + bool ExtendedTransportProtocolManager::send_clear_to_send(std::shared_ptr &session) const { - std::uint32_t nextPacketNumber = session.get_last_packet_number() + 1; + std::uint32_t nextPacketNumber = session->get_last_packet_number() + 1; const std::array buffer{ CLEAR_TO_SEND_MULTIPLEXOR, - session.get_cts_number_of_packet_limit(), + session->get_cts_number_of_packet_limit(), static_cast(nextPacketNumber & 0xFF), static_cast((nextPacketNumber >> 8) & 0xFF), static_cast((nextPacketNumber >> 16) & 0xFF), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + static_cast(session->get_parameter_group_number() & 0xFF), + static_cast((session->get_parameter_group_number() >> 8) & 0xFF), + static_cast((session->get_parameter_group_number() >> 16) & 0xFF) }; bool retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session - session.get_source(), + std::static_pointer_cast(session->get_destination()), // Since we're the receiving side, we are the destination of the session + session->get_source(), CANIdentifier::CANPriority::PriorityLowest7); if (retVal) { - session.set_acknowledged_packet_number(session.get_last_packet_number()); + session->set_acknowledged_packet_number(session->get_last_packet_number()); } return retVal; } - bool isobus::ExtendedTransportProtocolManager::send_data_packet_offset(ExtendedTransportProtocolSession &session) const + bool isobus::ExtendedTransportProtocolManager::send_data_packet_offset(std::shared_ptr &session) const { std::uint8_t packetsThisSegment = 0xFF; - if (packetsThisSegment > session.get_number_of_remaining_packets()) + if (packetsThisSegment > session->get_number_of_remaining_packets()) { - packetsThisSegment = static_cast(session.get_number_of_remaining_packets()); + packetsThisSegment = static_cast(session->get_number_of_remaining_packets()); } - if (packetsThisSegment > session.get_cts_number_of_packet_limit()) + if (packetsThisSegment > session->get_cts_number_of_packet_limit()) { - packetsThisSegment = session.get_cts_number_of_packet_limit(); + packetsThisSegment = session->get_cts_number_of_packet_limit(); } else if (packetsThisSegment > 16) { @@ -871,31 +841,31 @@ namespace isobus const std::array buffer{ DATA_PACKET_OFFSET_MULTIPLXOR, packetsThisSegment, - static_cast(session.get_last_packet_number()), - static_cast((session.get_last_packet_number() >> 8) & 0xFF), - static_cast((session.get_last_packet_number() >> 16) & 0xFF), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + static_cast(session->get_last_packet_number()), + static_cast((session->get_last_packet_number() >> 8) & 0xFF), + static_cast((session->get_last_packet_number() >> 16) & 0xFF), + static_cast(session->get_parameter_group_number() & 0xFF), + static_cast((session->get_parameter_group_number() >> 8) & 0xFF), + static_cast((session->get_parameter_group_number() >> 16) & 0xFF) }; bool retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_source()), - session.get_destination(), + std::static_pointer_cast(session->get_source()), + session->get_destination(), CANIdentifier::CANPriority::PriorityLowest7); if (retVal) { - session.set_dpo_number_of_packets(packetsThisSegment); - session.set_sequence_number_offset(session.get_last_packet_number()); - session.set_last_sequency_number(0); + session->set_dpo_number_of_packets(packetsThisSegment); + session->set_sequence_number_offset(session->get_last_packet_number()); + session->set_last_sequency_number(0); } return retVal; } - bool ExtendedTransportProtocolManager::send_end_of_session_acknowledgement(const ExtendedTransportProtocolSession &session) const + bool ExtendedTransportProtocolManager::send_end_of_session_acknowledgement(std::shared_ptr &session) const { - std::uint32_t messageLength = session.get_message_length(); - std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); + std::uint32_t messageLength = session->get_message_length(); + std::uint32_t parameterGroupNumber = session->get_parameter_group_number(); const std::array buffer{ END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, @@ -910,36 +880,30 @@ namespace isobus return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::ExtendedTransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session - session.get_source(), + std::static_pointer_cast(session->get_destination()), // Since we're the receiving side, we are the destination of the session + session->get_source(), CANIdentifier::CANPriority::PriorityLowest7); } bool ExtendedTransportProtocolManager::has_session(std::shared_ptr source, std::shared_ptr destination) { - return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const ExtendedTransportProtocolSession &session) { - return session.matches(source, destination); + return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { + return session->matches(source, destination); }); } - ExtendedTransportProtocolManager::ExtendedTransportProtocolSession *ExtendedTransportProtocolManager::get_session(std::shared_ptr source, - std::shared_ptr destination) + std::shared_ptr ExtendedTransportProtocolManager::get_session(std::shared_ptr source, + std::shared_ptr destination) { - auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const ExtendedTransportProtocolSession &session) { - return session.matches(source, destination); + auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { + return session->matches(source, destination); }); // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored - return (activeSessions.end() != result) ? &(*result) : nullptr; + return (activeSessions.end() != result) ? (*result) : nullptr; } - std::vector ExtendedTransportProtocolManager::get_sessions() const + const std::vector> &ExtendedTransportProtocolManager::get_sessions() const { - std::vector sessions; - sessions.reserve(activeSessions.size()); - for (auto &session : activeSessions) - { - sessions.push_back(&session); - } - return sessions; + return activeSessions; } } diff --git a/isobus/src/can_network_manager.cpp b/isobus/src/can_network_manager.cpp index fb386bdd..9f35f3eb 100644 --- a/isobus/src/can_network_manager.cpp +++ b/isobus/src/can_network_manager.cpp @@ -445,18 +445,11 @@ namespace isobus return retVal; } - std::vector isobus::CANNetworkManager::get_active_transport_protocol_sessions(std::uint8_t canPortIndex) const + std::list> isobus::CANNetworkManager::get_active_transport_protocol_sessions(std::uint8_t canPortIndex) const { - std::vector retVal; - - for (auto currentSession : transportProtocols[canPortIndex]->get_sessions()) - { - retVal.push_back(currentSession); - } - for (auto currentSession : extendedTransportProtocols[canPortIndex]->get_sessions()) - { - retVal.push_back(currentSession); - } + std::list> retVal; + retVal.insert(retVal.end(), transportProtocols[canPortIndex]->get_sessions().begin(), transportProtocols[canPortIndex]->get_sessions().end()); + retVal.insert(retVal.end(), extendedTransportProtocols[canPortIndex]->get_sessions().begin(), extendedTransportProtocols[canPortIndex]->get_sessions().end()); return retVal; } diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index 660349b5..8cb299c9 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -29,29 +29,6 @@ namespace isobus { } - // Explicitly define the move constructor and assignment operator to ensure that the base class is moved correctly - // See https://stackoverflow.com/a/15351528 for why this is necessary - TransportProtocolManager::TransportProtocolSession::TransportProtocolSession(TransportProtocolManager::TransportProtocolSession &&obj) noexcept : - TransportProtocolSessionBase(std::move(obj)), - state(obj.state), - lastSequenceNumber(obj.lastSequenceNumber), - lastAcknowledgedPacketNumber(obj.lastAcknowledgedPacketNumber), - clearToSendPacketCount(obj.clearToSendPacketCount), - clearToSendPacketCountMax(obj.clearToSendPacketCountMax) - { - } - - TransportProtocolManager::TransportProtocolSession &TransportProtocolManager::TransportProtocolSession::operator=(TransportProtocolSession &&obj) noexcept - { - TransportProtocolSessionBase::operator=(std::move(obj)); - clearToSendPacketCountMax = obj.clearToSendPacketCountMax; - clearToSendPacketCount = obj.clearToSendPacketCount; - lastSequenceNumber = obj.lastSequenceNumber; - lastAcknowledgedPacketNumber = obj.lastAcknowledgedPacketNumber; - state = obj.state; - return *this; - } - TransportProtocolManager::StateMachineState TransportProtocolManager::TransportProtocolSession::get_state() const { return state; @@ -78,11 +55,6 @@ namespace isobus return transferred; } - float TransportProtocolManager::TransportProtocolSession::get_percentage_bytes_transferred() const - { - return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()); - } - void TransportProtocolManager::TransportProtocolSession::set_state(StateMachineState value) { state = value; @@ -178,28 +150,28 @@ namespace isobus if (nullptr != oldSession) { CANStackLogger::warn("[TP]: Received Broadcast Announcement Message (BAM) while a session already existed for this source (%hu), overwriting for 0x%05X...", source->get_address(), parameterGroupNumber); - close_session(*oldSession, false); + close_session(oldSession, false); } - TransportProtocolSession newSession(TransportProtocolSession::Direction::Receive, - std::unique_ptr(new CANMessageDataVector(totalMessageSize)), - parameterGroupNumber, - totalMessageSize, - 0xFF, // Arbitrary - unused for broadcast - source, - nullptr, // Global destination - nullptr, // No callback - nullptr); - - if (newSession.get_total_number_of_packets() != totalNumberOfPackets) + auto newSession = std::make_shared(TransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + 0xFF, // Arbitrary - unused for broadcast + source, + nullptr, // Global destination + nullptr, // No callback + nullptr); + + if (newSession->get_total_number_of_packets() != totalNumberOfPackets) { CANStackLogger::warn("[TP]: Received Broadcast Announcement Message (BAM) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); } else { - newSession.set_state(StateMachineState::WaitForDataTransferPacket); - activeSessions.push_back(std::move(newSession)); - update_state_machine(activeSessions.back()); + newSession->set_state(StateMachineState::WaitForDataTransferPacket); + activeSessions.push_back(newSession); + update_state_machine(newSession); CANStackLogger::debug("[TP]: New rx broadcast message session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); } } @@ -226,12 +198,12 @@ namespace isobus if (oldSession->get_parameter_group_number() != parameterGroupNumber) { CANStackLogger::error("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination, aborting for 0x%05X...", parameterGroupNumber); - abort_session(*oldSession, ConnectionAbortReason::AlreadyInCMSession); + abort_session(oldSession, ConnectionAbortReason::AlreadyInCMSession); } else { CANStackLogger::warn("[TP]: Received Request To Send (RTS) while a session already existed for this source and destination and parameterGroupNumber, overwriting for 0x%05X...", parameterGroupNumber); - close_session(*oldSession, false); + close_session(oldSession, false); } } @@ -243,27 +215,27 @@ namespace isobus clearToSendPacketMax = configuration->get_number_of_packets_per_cts_message(); } - TransportProtocolSession newSession(TransportProtocolSession::Direction::Receive, - std::unique_ptr(new CANMessageDataVector(totalMessageSize)), - parameterGroupNumber, - totalMessageSize, - clearToSendPacketMax, - source, - destination, - nullptr, // No callback - nullptr); - - if (newSession.get_total_number_of_packets() != totalNumberOfPackets) + auto newSession = std::make_shared(TransportProtocolSession::Direction::Receive, + std::unique_ptr(new CANMessageDataVector(totalMessageSize)), + parameterGroupNumber, + totalMessageSize, + clearToSendPacketMax, + source, + destination, + nullptr, // No callback + nullptr); + + if (newSession->get_total_number_of_packets() != totalNumberOfPackets) { CANStackLogger::error("[TP]: Received Request To Send (RTS) for 0x%05X with a bad number of packets, aborting...", parameterGroupNumber); abort_session(newSession, ConnectionAbortReason::AnyOtherError); } else { - newSession.set_state(StateMachineState::SendClearToSend); - activeSessions.push_back(std::move(newSession)); + newSession->set_state(StateMachineState::SendClearToSend); + activeSessions.push_back(newSession); CANStackLogger::debug("[TP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); - update_state_machine(activeSessions.back()); + update_state_machine(newSession); } } } @@ -280,19 +252,19 @@ namespace isobus if (session->get_parameter_group_number() != parameterGroupNumber) { CANStackLogger::error("[TP]: Received a Clear To Send (CTS) message for 0x%05X while a session already existed for this source and destination, sending abort for both...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); send_abort(std::static_pointer_cast(destination), source, parameterGroupNumber, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else if (nextPacketNumber > session->get_total_number_of_packets()) { CANStackLogger::error("[TP]: Received a Clear To Send (CTS) message for 0x%05X with a bad sequence number, aborting...", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::BadSequenceNumber); + abort_session(session, ConnectionAbortReason::BadSequenceNumber); } else if (StateMachineState::WaitForClearToSend != session->state) { // The session exists, but we're not in the right state to receive a CTS, so we must abort - CANStackLogger::warn("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); - abort_session(*session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); + CANStackLogger::warn("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); + abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else { @@ -304,7 +276,7 @@ namespace isobus if (0 != packetsToBeSent) { session->set_state(StateMachineState::SendDataTransferPackets); - update_state_machine(*session); + update_state_machine(session); } } } @@ -325,7 +297,7 @@ namespace isobus if (StateMachineState::WaitForEndOfMessageAcknowledge == session->state) { session->state = StateMachineState::None; - close_session(*session, true); + close_session(session, true); CANStackLogger::debug("[TP]: Completed tx session for 0x%05X from %hu", parameterGroupNumber, source->get_address()); } else @@ -352,14 +324,14 @@ namespace isobus { foundSession = true; CANStackLogger::error("[TP]: Received an abort (reason=%hu) for an rx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); - close_session(*session, false); + close_session(session, false); } session = get_session(destination, source); if ((nullptr != session) && (session->get_parameter_group_number() == parameterGroupNumber)) { foundSession = true; CANStackLogger::error("[TP]: Received an abort (reason=%hu) for a tx session for parameterGroupNumber 0x%05X", static_cast(reason), parameterGroupNumber); - close_session(*session, false); + close_session(session, false); } if (!foundSession) @@ -497,12 +469,12 @@ namespace isobus if (StateMachineState::WaitForDataTransferPacket != session->state) { CANStackLogger::warn("[TP]: Received a Data Transfer message from %hu while not expecting one, sending abort", source->get_address()); - abort_session(*session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); + abort_session(session, ConnectionAbortReason::UnexpectedDataTransferPacketReceived); } else if (sequenceNumber == session->get_last_sequence_number()) { CANStackLogger::error("[TP]: Aborting rx session for 0x%05X due to duplicate sequence number", session->get_parameter_group_number()); - abort_session(*session, ConnectionAbortReason::DuplicateSequenceNumber); + abort_session(session, ConnectionAbortReason::DuplicateSequenceNumber); } else if (sequenceNumber == (session->get_last_sequence_number() + 1)) { @@ -530,7 +502,7 @@ namespace isobus // Send End of Message Acknowledgement for sessions with specific destination only if (!message.is_broadcast()) { - send_end_of_session_acknowledgement(*session); + send_end_of_session_acknowledgement(session); } // Construct the completed message @@ -545,18 +517,18 @@ namespace isobus completedMessage.set_data(data.data().begin(), static_cast(data.size())); canMessageReceivedCallback(completedMessage); - close_session(*session, true); + close_session(session, true); CANStackLogger::debug("[TP]: Completed rx session for 0x%05X from %hu", session->get_parameter_group_number(), source->get_address()); } else if (session->get_cts_number_of_packets_remaining() == 0) { - send_clear_to_send(*session); + send_clear_to_send(session); } } else { CANStackLogger::error("[TP]: Aborting rx session for 0x%05X due to bad sequence number", session->get_parameter_group_number()); - abort_session(*session, ConnectionAbortReason::BadSequenceNumber); + abort_session(session, ConnectionAbortReason::BadSequenceNumber); } } else if (!message.is_broadcast()) @@ -613,20 +585,20 @@ namespace isobus data = data->copy_if_not_owned(std::move(data)); auto dataLength = static_cast(data->size()); - TransportProtocolSession session(TransportProtocolSession::Direction::Transmit, - std::move(data), - parameterGroupNumber, - dataLength, - configuration->get_number_of_packets_per_cts_message(), - source, - destination, - sessionCompleteCallback, - parentPointer); - - if (session.is_broadcast()) + auto session = std::make_shared(TransportProtocolSession::Direction::Transmit, + std::move(data), + parameterGroupNumber, + dataLength, + configuration->get_number_of_packets_per_cts_message(), + source, + destination, + sessionCompleteCallback, + parentPointer); + + if (session->is_broadcast()) { // Broadcast message - session.set_state(StateMachineState::SendBroadcastAnnounce); + session->set_state(StateMachineState::SendBroadcastAnnounce); CANStackLogger::debug("[TP]: New broadcast tx session for 0x%05X. Source: %hu", parameterGroupNumber, source->get_address()); @@ -634,14 +606,14 @@ namespace isobus else { // Destination specific message - session.set_state(StateMachineState::SendRequestToSend); + session->set_state(StateMachineState::SendRequestToSend); CANStackLogger::debug("[TP]: New tx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); } - activeSessions.push_back(std::move(session)); - update_state_machine(activeSessions.back()); + activeSessions.push_back(session); + update_state_machine(session); return true; } @@ -650,29 +622,29 @@ namespace isobus // We use a fancy for loop here to allow us to remove sessions from the list while iterating for (std::size_t i = activeSessions.size(); i > 0; i--) { - auto &session = activeSessions.at(i - 1); - if (!session.get_source()->get_address_valid()) + auto session = activeSessions.at(i - 1); + if (!session->get_source()->get_address_valid()) { CANStackLogger::warn("[TP]: Closing active session as the source control function is no longer valid"); close_session(session, false); } - else if (!session.is_broadcast() && !session.get_destination()->get_address_valid()) + else if (!session->is_broadcast() && !session->get_destination()->get_address_valid()) { CANStackLogger::warn("[TP]: Closing active session as the destination control function is no longer valid"); close_session(session, false); } - else if (StateMachineState::None != session.state) + else if (StateMachineState::None != session->state) { update_state_machine(session); } } } - void TransportProtocolManager::send_data_transfer_packets(TransportProtocolSession &session) + void TransportProtocolManager::send_data_transfer_packets(std::shared_ptr &session) { std::array buffer; - std::uint8_t framesToSend = session.get_cts_number_of_packets_remaining(); - if (session.is_broadcast()) + std::uint8_t framesToSend = session->get_cts_number_of_packets_remaining(); + if (session->is_broadcast()) { framesToSend = 1; } @@ -684,15 +656,15 @@ namespace isobus // Try and send packets for (std::uint8_t i = 0; i < framesToSend; i++) { - buffer[0] = session.get_last_sequence_number() + 1; + buffer[0] = session->get_last_sequence_number() + 1; - std::uint16_t dataOffset = session.get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; + std::uint16_t dataOffset = session->get_last_packet_number() * PROTOCOL_BYTES_PER_FRAME; for (std::uint8_t j = 0; j < PROTOCOL_BYTES_PER_FRAME; j++) { std::uint16_t index = dataOffset + j; - if (index < session.get_message_length()) + if (index < session->get_message_length()) { - buffer[1 + j] = session.get_data().get_byte(index); + buffer[1 + j] = session->get_data().get_byte(index); } else { @@ -702,11 +674,11 @@ namespace isobus if (sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolDataTransfer), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_source()), - session.get_destination(), + std::static_pointer_cast(session->get_source()), + session->get_destination(), CANIdentifier::CANPriority::PriorityLowest7)) { - session.set_last_sequency_number(session.get_last_sequence_number() + 1); + session->set_last_sequency_number(session->get_last_sequence_number() + 1); } else { @@ -715,27 +687,27 @@ namespace isobus } } - if (session.get_number_of_remaining_packets() == 0) + if (session->get_number_of_remaining_packets() == 0) { - if (session.is_broadcast()) + if (session->is_broadcast()) { - CANStackLogger::debug("[TP]: Completed broadcast tx session for 0x%05X", session.get_parameter_group_number()); + CANStackLogger::debug("[TP]: Completed broadcast tx session for 0x%05X", session->get_parameter_group_number()); close_session(session, true); } else { - session.set_state(StateMachineState::WaitForEndOfMessageAcknowledge); + session->set_state(StateMachineState::WaitForEndOfMessageAcknowledge); } } - else if (session.get_cts_number_of_packets_remaining() == 0) + else if (session->get_cts_number_of_packets_remaining() == 0) { - session.set_state(StateMachineState::WaitForClearToSend); + session->set_state(StateMachineState::WaitForClearToSend); } } - void TransportProtocolManager::update_state_machine(TransportProtocolSession &session) + void TransportProtocolManager::update_state_machine(std::shared_ptr &session) { - switch (session.state) + switch (session->state) { case StateMachineState::None: break; @@ -744,7 +716,7 @@ namespace isobus { if (send_broadcast_announce_message(session)) { - session.set_state(StateMachineState::SendDataTransferPackets); + session->set_state(StateMachineState::SendDataTransferPackets); } } break; @@ -753,17 +725,17 @@ namespace isobus { if (send_request_to_send(session)) { - session.set_state(StateMachineState::WaitForClearToSend); + session->set_state(StateMachineState::WaitForClearToSend); } } break; case StateMachineState::WaitForClearToSend: { - if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) + if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected CTS)", session.get_parameter_group_number()); - if (session.get_cts_number_of_packets() > 0) + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected CTS)", session->get_parameter_group_number()); + if (session->get_cts_number_of_packets() > 0) { // A connection is only considered established if we've received at least one CTS before // And we can only abort a connection if it's considered established @@ -781,14 +753,14 @@ namespace isobus { if (send_clear_to_send(session)) { - session.set_state(StateMachineState::WaitForDataTransferPacket); + session->set_state(StateMachineState::WaitForDataTransferPacket); } } break; case StateMachineState::SendDataTransferPackets: { - if (session.is_broadcast() && (session.get_time_since_last_update() < configuration->get_minimum_time_between_transport_protocol_bam_frames())) + if (session->is_broadcast() && (session->get_time_since_last_update() < configuration->get_minimum_time_between_transport_protocol_bam_frames())) { // Need to wait before sending the next data frame of the broadcast session } @@ -801,19 +773,19 @@ namespace isobus case StateMachineState::WaitForDataTransferPacket: { - if (session.is_broadcast()) + if (session->is_broadcast()) { // Broadcast message timeout check - if (session.get_time_since_last_update() > T1_TIMEOUT_MS) + if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { CANStackLogger::warn("[TP]: Broadcast rx session timeout"); close_session(session, false); } } - else if (session.get_cts_number_of_packets_remaining() == session.get_cts_number_of_packets()) + else if (session->get_cts_number_of_packets_remaining() == session->get_cts_number_of_packets()) { // Waiting to receive the first data frame after CTS - if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) + if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected first data frame)"); abort_session(session, ConnectionAbortReason::Timeout); @@ -822,7 +794,7 @@ namespace isobus else { // Waiting on sequencial data frames - if (session.get_time_since_last_update() > T1_TIMEOUT_MS) + if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); abort_session(session, ConnectionAbortReason::Timeout); @@ -833,9 +805,9 @@ namespace isobus case StateMachineState::WaitForEndOfMessageAcknowledge: { - if (session.get_time_since_last_update() > T2_T3_TIMEOUT_MS) + if (session->get_time_since_last_update() > T2_T3_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session.get_parameter_group_number()); + CANStackLogger::error("[TP]: Timeout tx session for 0x%05X (expected EOMA)", session->get_parameter_group_number()); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -843,25 +815,25 @@ namespace isobus } } - bool TransportProtocolManager::abort_session(const TransportProtocolSession &session, ConnectionAbortReason reason) + bool TransportProtocolManager::abort_session(std::shared_ptr &session, ConnectionAbortReason reason) { bool retVal = false; std::shared_ptr myControlFunction; std::shared_ptr partnerControlFunction; - if (TransportProtocolSession::Direction::Transmit == session.get_direction()) + if (TransportProtocolSession::Direction::Transmit == session->get_direction()) { - myControlFunction = std::static_pointer_cast(session.get_source()); - partnerControlFunction = session.get_destination(); + myControlFunction = std::static_pointer_cast(session->get_source()); + partnerControlFunction = session->get_destination(); } else { - myControlFunction = std::static_pointer_cast(session.get_destination()); - partnerControlFunction = session.get_source(); + myControlFunction = std::static_pointer_cast(session->get_destination()); + partnerControlFunction = session->get_source(); } if ((nullptr != myControlFunction) && (nullptr != partnerControlFunction)) { - retVal = send_abort(myControlFunction, partnerControlFunction, session.get_parameter_group_number(), reason); + retVal = send_abort(myControlFunction, partnerControlFunction, session->get_parameter_group_number(), reason); } close_session(session, false); return retVal; @@ -889,9 +861,9 @@ namespace isobus CANIdentifier::CANPriority::PriorityLowest7); } - void TransportProtocolManager::close_session(const TransportProtocolSession &session, bool successful) + void TransportProtocolManager::close_session(std::shared_ptr &session, bool successful) { - session.complete(successful); + session->complete(successful); auto sessionLocation = std::find(activeSessions.begin(), activeSessions.end(), session); if (activeSessions.end() != sessionLocation) @@ -901,52 +873,52 @@ namespace isobus } } - bool TransportProtocolManager::send_broadcast_announce_message(const TransportProtocolSession &session) const + bool TransportProtocolManager::send_broadcast_announce_message(std::shared_ptr &session) const { const std::array buffer{ BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - session.get_total_number_of_packets(), + static_cast(session->get_message_length() & 0xFF), + static_cast((session->get_message_length() >> 8) & 0xFF), + session->get_total_number_of_packets(), 0xFF, - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + static_cast(session->get_parameter_group_number() & 0xFF), + static_cast((session->get_parameter_group_number() >> 8) & 0xFF), + static_cast((session->get_parameter_group_number() >> 16) & 0xFF) }; return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_source()), + std::static_pointer_cast(session->get_source()), nullptr, CANIdentifier::CANPriority::PriorityLowest7); } - bool TransportProtocolManager::send_request_to_send(const TransportProtocolSession &session) const + bool TransportProtocolManager::send_request_to_send(std::shared_ptr &session) const { const std::array buffer{ REQUEST_TO_SEND_MULTIPLEXOR, - static_cast(session.get_message_length() & 0xFF), - static_cast((session.get_message_length() >> 8) & 0xFF), - session.get_total_number_of_packets(), - session.get_rts_number_of_packet_limit(), - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + static_cast(session->get_message_length() & 0xFF), + static_cast((session->get_message_length() >> 8) & 0xFF), + session->get_total_number_of_packets(), + session->get_rts_number_of_packet_limit(), + static_cast(session->get_parameter_group_number() & 0xFF), + static_cast((session->get_parameter_group_number() >> 8) & 0xFF), + static_cast((session->get_parameter_group_number() >> 16) & 0xFF) }; return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_source()), - session.get_destination(), + std::static_pointer_cast(session->get_source()), + session->get_destination(), CANIdentifier::CANPriority::PriorityLowest7); } - bool TransportProtocolManager::send_clear_to_send(TransportProtocolSession &session) const + bool TransportProtocolManager::send_clear_to_send(std::shared_ptr &session) const { bool retVal = false; - std::uint8_t packetsThisSegment = session.get_number_of_remaining_packets(); - if (packetsThisSegment > session.get_rts_number_of_packet_limit()) + std::uint8_t packetsThisSegment = session->get_number_of_remaining_packets(); + if (packetsThisSegment > session->get_rts_number_of_packet_limit()) { - packetsThisSegment = session.get_rts_number_of_packet_limit(); + packetsThisSegment = session->get_rts_number_of_packet_limit(); } else if (packetsThisSegment > 16) { @@ -957,36 +929,36 @@ namespace isobus const std::array buffer{ CLEAR_TO_SEND_MULTIPLEXOR, packetsThisSegment, - static_cast(session.get_last_packet_number() + 1), + static_cast(session->get_last_packet_number() + 1), 0xFF, 0xFF, - static_cast(session.get_parameter_group_number() & 0xFF), - static_cast((session.get_parameter_group_number() >> 8) & 0xFF), - static_cast((session.get_parameter_group_number() >> 16) & 0xFF) + static_cast(session->get_parameter_group_number() & 0xFF), + static_cast((session->get_parameter_group_number() >> 8) & 0xFF), + static_cast((session->get_parameter_group_number() >> 16) & 0xFF) }; retVal = sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session - session.get_source(), + std::static_pointer_cast(session->get_destination()), // Since we're the receiving side, we are the destination of the session + session->get_source(), CANIdentifier::CANPriority::PriorityLowest7); if (retVal) { - session.set_cts_number_of_packets(packetsThisSegment); - session.set_acknowledged_packet_number(session.get_last_packet_number()); + session->set_cts_number_of_packets(packetsThisSegment); + session->set_acknowledged_packet_number(session->get_last_packet_number()); } return retVal; } - bool TransportProtocolManager::send_end_of_session_acknowledgement(const TransportProtocolSession &session) const + bool TransportProtocolManager::send_end_of_session_acknowledgement(std::shared_ptr &session) const { - std::uint32_t messageLength = session.get_message_length(); - std::uint32_t parameterGroupNumber = session.get_parameter_group_number(); + std::uint32_t messageLength = session->get_message_length(); + std::uint32_t parameterGroupNumber = session->get_parameter_group_number(); const std::array buffer{ END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR, static_cast(messageLength & 0xFF), static_cast((messageLength >> 8) & 0xFF), - session.get_total_number_of_packets(), + session->get_total_number_of_packets(), 0xFF, static_cast(parameterGroupNumber & 0xFF), static_cast((parameterGroupNumber >> 8) & 0xFF), @@ -995,37 +967,31 @@ namespace isobus return sendCANFrameCallback(static_cast(CANLibParameterGroupNumber::TransportProtocolConnectionManagement), CANDataSpan(buffer.data(), buffer.size()), - std::static_pointer_cast(session.get_destination()), // Since we're the receiving side, we are the destination of the session + std::static_pointer_cast(session->get_destination()), // Since we're the receiving side, we are the destination of the session - session.get_source(), + session->get_source(), CANIdentifier::CANPriority::PriorityLowest7); } bool TransportProtocolManager::has_session(std::shared_ptr source, std::shared_ptr destination) { - return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const TransportProtocolSession &session) { - return session.matches(source, destination); + return std::any_of(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { + return session->matches(source, destination); }); } - TransportProtocolManager::TransportProtocolSession *TransportProtocolManager::get_session(std::shared_ptr source, - std::shared_ptr destination) + std::shared_ptr TransportProtocolManager::get_session(std::shared_ptr source, + std::shared_ptr destination) { - auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const TransportProtocolSession &session) { - return session.matches(source, destination); + auto result = std::find_if(activeSessions.begin(), activeSessions.end(), [&](const std::shared_ptr &session) { + return session->matches(source, destination); }); // Instead of returning a pointer, we return by reference to indicate it should not be deleted or stored - return (activeSessions.end() != result) ? &(*result) : nullptr; + return (activeSessions.end() != result) ? (*result) : nullptr; } - std::vector TransportProtocolManager::get_sessions() const + const std::vector> &TransportProtocolManager::get_sessions() const { - std::vector sessions; - sessions.reserve(activeSessions.size()); - for (auto &session : activeSessions) - { - sessions.push_back(&session); - } - return sessions; + return activeSessions; } } diff --git a/isobus/src/can_transport_protocol_base.cpp b/isobus/src/can_transport_protocol_base.cpp index 4c86b2ab..1b7d2078 100644 --- a/isobus/src/can_transport_protocol_base.cpp +++ b/isobus/src/can_transport_protocol_base.cpp @@ -62,6 +62,11 @@ namespace isobus return source; } + float TransportProtocolSessionBase::get_percentage_bytes_transferred() const + { + return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()); + } + std::shared_ptr TransportProtocolSessionBase::get_destination() const { return destination; From 6f3ac4402305947013506c81ec43e53abfb10ef9 Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Wed, 20 Dec 2023 21:03:57 +0100 Subject: [PATCH 6/9] fix(tp/etp): remove some of the immediate updates to allow aborts to come through first --- isobus/src/can_extended_transport_protocol.cpp | 2 -- isobus/src/can_transport_protocol.cpp | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index 3c900ba7..64c3a1f7 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -208,7 +208,6 @@ namespace isobus if (0 != packetsToBeSent) { session->set_state(StateMachineState::SendDataPacketOffset); - update_state_machine(session); } } } @@ -686,7 +685,6 @@ namespace isobus if (send_data_packet_offset(session)) { session->set_state(StateMachineState::SendDataTransferPackets); - update_state_machine(session); // Immediately update the state machine to send the next message } } break; diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index 8cb299c9..c8bdf5fc 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -263,7 +263,7 @@ namespace isobus else if (StateMachineState::WaitForClearToSend != session->state) { // The session exists, but we're not in the right state to receive a CTS, so we must abort - CANStackLogger::warn("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session->", parameterGroupNumber); + CANStackLogger::warn("[TP]: Received a Clear To Send (CTS) message for 0x%05X, but not expecting one, aborting session.", parameterGroupNumber); abort_session(session, ConnectionAbortReason::ClearToSendReceivedWhileTransferInProgress); } else @@ -276,7 +276,6 @@ namespace isobus if (0 != packetsToBeSent) { session->set_state(StateMachineState::SendDataTransferPackets); - update_state_machine(session); } } } From 603c7368f4c5de1cee75bc0bd71ea54d5770bb4c Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Sun, 7 Jan 2024 15:20:50 +0100 Subject: [PATCH 7/9] core: fix callback data not loading buffer correctly Also stopped transport protocols listening to messages not destined to them --- examples/transport_layer/main.cpp | 7 ++- .../can_extended_transport_protocol.hpp | 22 +++----- .../isobus/isobus/can_message_data.hpp | 17 +----- .../isobus/can_network_configuration.hpp | 17 +++--- .../isobus/isobus/can_transport_protocol.hpp | 21 +++----- .../isobus/can_transport_protocol_base.hpp | 6 +-- .../src/can_extended_transport_protocol.cpp | 13 +++-- isobus/src/can_message_data.cpp | 12 +++-- isobus/src/can_network_configuration.cpp | 8 +-- isobus/src/can_transport_protocol.cpp | 6 +-- isobus/src/can_transport_protocol_base.cpp | 9 ++-- test/helpers/control_function_helpers.cpp | 16 ++++++ test/helpers/control_function_helpers.hpp | 2 + test/transport_protocol_tests.cpp | 54 +++++++++---------- 14 files changed, 106 insertions(+), 104 deletions(-) diff --git a/examples/transport_layer/main.cpp b/examples/transport_layer/main.cpp index c5d2d912..17a5aaae 100644 --- a/examples/transport_layer/main.cpp +++ b/examples/transport_layer/main.cpp @@ -32,6 +32,11 @@ int main() { std::signal(SIGINT, signal_handler); +#ifndef ISOBUS_VIRTUALCAN_AVAILABLE + std::cout << "This example requires the VirtualCAN plugin to be available. If using CMake, set the `-DCAN_DRIVER=VirtualCAN`." << std::endl; + return -1; +#endif + std::shared_ptr originatorDriver = std::make_shared("test-channel"); std::shared_ptr recipientDriver = std::make_shared("test-channel"); @@ -181,7 +186,7 @@ void check_can_message(const CANMessage &message, void *) { if (message.get_data()[i] != (i % 0xFF)) { - std::cout << std::endl // End the progress bar + std::cerr << std::endl // End the progress bar << "Received CAN with incorrect data!!!" << std::endl; return; } diff --git a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp index ca052bc2..24669bcf 100644 --- a/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_extended_transport_protocol.hpp @@ -18,14 +18,10 @@ namespace isobus { - //================================================================================================ - /// @class ExtendedTransportProtocolManager - /// /// @brief A class that handles the ISO11783 extended transport protocol. /// @details This class handles transmission and reception of CAN messages more than 1785 bytes. /// Simply call Simply call `CANNetworkManager::send_can_message()` /// with an appropriate data length, and the protocol will be automatically selected to be used. - //================================================================================================ class ExtendedTransportProtocolManager { public: @@ -64,11 +60,7 @@ namespace isobus AnyOtherError = 250 ///< Any reason not defined in the standard }; - //================================================================================================ - /// @class ExtendedTransportProtocolSession - /// /// @brief A storage object to keep track of session information internally - //================================================================================================ class ExtendedTransportProtocolSession : public TransportProtocolSessionBase { public: @@ -144,18 +136,18 @@ namespace isobus private: StateMachineState state = StateMachineState::None; ///< The state machine state for this session - std::uint8_t lastSequenceNumber = 0; ///< The last processed sequence number for this set of packets - std::uint32_t sequenceNumberOffset = 0; ///< The offset of the sequence number relative to the packet number std::uint32_t lastAcknowledgedPacketNumber = 0; ///< The last acknowledged packet number by the receiver + std::uint32_t sequenceNumberOffset = 0; ///< The offset of the sequence number relative to the packet number + std::uint8_t lastSequenceNumber = 0; ///< The last processed sequence number for this set of packets std::uint8_t dataPacketOffsetPacketCount = 0; ///< The number of packets that will be sent with the current DPO std::uint8_t clearToSendPacketCountLimit = 0xFF; ///< The max packets that can be sent per DPO as indicated by the CTS message }; - static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 20; ///< ETP.CM_RTS Multiplexor - static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 21; ///< ETP.CM_CTS Multiplexor - static constexpr std::uint32_t DATA_PACKET_OFFSET_MULTIPLXOR = 22; ///< ETP.CM_DPO Multiplexor - static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 23; ///< TP.CM_EOMA Multiplexor - static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 255; ///< Abort multiplexor + static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 0x14; ///< (20) ETP.CM_RTS Multiplexor + static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 0x15; ///< (21) ETP.CM_CTS Multiplexor + static constexpr std::uint32_t DATA_PACKET_OFFSET_MULTIPLXOR = 0x16; ///< (22) ETP.CM_DPO Multiplexor + static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 0x17; ///< (23) TP.CM_EOMA Multiplexor + static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 0xFF; ///< (255) Abort multiplexor static constexpr std::uint32_t MAX_PROTOCOL_DATA_LENGTH = 117440505; ///< The max number of bytes that this protocol can transfer static constexpr std::uint16_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard static constexpr std::uint16_t T2_T3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard diff --git a/isobus/include/isobus/isobus/can_message_data.hpp b/isobus/include/isobus/isobus/can_message_data.hpp index d77cf1d7..bb3c0048 100644 --- a/isobus/include/isobus/isobus/can_message_data.hpp +++ b/isobus/include/isobus/isobus/can_message_data.hpp @@ -21,11 +21,7 @@ namespace isobus { - //================================================================================================ - /// @class CANMessageData - /// /// @brief A interface class that represents data payload of a CAN message of arbitrary length. - //================================================================================================ class CANMessageData { public: @@ -47,11 +43,7 @@ namespace isobus virtual std::unique_ptr copy_if_not_owned(std::unique_ptr self) const = 0; }; - //================================================================================================ - /// @class CANMessageDataVector - /// /// @brief A class that represents data of a CAN message by holding a vector of bytes. - //================================================================================================ class CANMessageDataVector : public CANMessageData , public std::vector { @@ -93,12 +85,8 @@ namespace isobus std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; }; - //================================================================================================ - /// @class CANMessageDataView - /// /// @brief A class that represents data of a CAN message by holding a view of an array of bytes. /// The view is not owned by this class, it is simply holding a pointer to the array of bytes. - //================================================================================================ class CANMessageDataView : public CANMessageData , public CANDataSpan { @@ -127,11 +115,7 @@ namespace isobus std::unique_ptr copy_if_not_owned(std::unique_ptr self) const override; }; - //================================================================================================ - /// @class CANMessageDataCallback - /// /// @brief A class that represents data of a CAN message by using a callback function. - //================================================================================================ class CANMessageDataCallback : public CANMessageData { public: @@ -166,6 +150,7 @@ namespace isobus std::vector buffer; ///< The buffer to store the data chunks. std::size_t bufferSize; ///< The size of the buffer. std::size_t dataOffset = 0; ///< The offset of the data in the buffer. + bool initialized = false; ///< Whether the buffer has been initialized. }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/can_network_configuration.hpp b/isobus/include/isobus/isobus/can_network_configuration.hpp index a7c92787..e0a710f0 100644 --- a/isobus/include/isobus/isobus/can_network_configuration.hpp +++ b/isobus/include/isobus/isobus/can_network_configuration.hpp @@ -37,7 +37,8 @@ namespace isobus /// @returns The max number of concurrent TP sessions std::uint32_t get_max_number_transport_protocol_sessions() const; - /// @brief Sets the minimum time to wait between sending BAM frames (default is 50 ms) + /// @brief Sets the minimum time to wait between sending BAM frames + /// (default is 50 ms for maximum J1939 compatibility) /// @details The acceptable range as defined by ISO-11783 is 10 to 200 ms. /// This is a minumum time, so if you set it to some value, like 10 ms, the /// stack will attempt to transmit it as close to that time as it can, but it is @@ -50,16 +51,16 @@ namespace isobus std::uint32_t get_minimum_time_between_transport_protocol_bam_frames() const; /// @brief Sets the max number of data frames the stack will use when - /// in an ETP session, between EDPO phases. The default is 255, - /// but decreasing it may reduce bus load at the expense of transfer time. + /// in an ETP session, between EDPO phases. The default is 16. + /// Note that the sending control function may choose to use a lower number of frames. /// @param[in] numberFrames The max number of data frames to use - void set_max_number_of_etp_frames_per_edpo(std::uint8_t numberFrames); + void set_number_of_packets_per_dpo_message(std::uint8_t numberFrames); /// @brief Returns the max number of data frames the stack will use when - /// in an ETP session, between EDPO phases. The default is 255, - /// but decreasing it may reduce bus load at the expense of transfer time. + /// in an ETP session, between EDPO phases. The default is 16. + /// Note that the sending control function may choose to use a lower number of frames. /// @returns The number of data frames the stack will use when sending ETP messages between EDPOs - std::uint8_t get_max_number_of_etp_frames_per_edpo() const; + std::uint8_t get_number_of_packets_per_dpo_message() const; /// @brief Sets the max number of data frames the stack will send from each /// transport layer protocol, per update. The default is 255, @@ -88,8 +89,8 @@ namespace isobus std::uint32_t maxNumberTransportProtocolSessions = 4; ///< The max number of TP sessions allowed std::uint32_t minimumTimeBetweenTransportProtocolBAMFrames = DEFAULT_BAM_PACKET_DELAY_TIME_MS; ///< The configurable time between BAM frames - std::uint8_t extendedTransportProtocolMaxNumberOfFramesPerEDPO = 0xFF; ///< Used to control throttling of ETP sessions. std::uint8_t networkManagerMaxFramesToSendPerUpdate = 0xFF; ///< Used to control the max number of transport layer frames added to the driver queue per network manager update + std::uint8_t numberOfPacketsPerDPOMessage = 16; ///< The number of packets per DPO message for ETP sessions std::uint8_t numberOfPacketsPerCTSMessage = 16; ///< The number of packets per CTS message for TP sessions }; } // namespace isobus diff --git a/isobus/include/isobus/isobus/can_transport_protocol.hpp b/isobus/include/isobus/isobus/can_transport_protocol.hpp index c51517c7..f07c8d62 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol.hpp @@ -18,19 +18,16 @@ namespace isobus { - //================================================================================================ - /// @class TransportProtocolManager - /// + /// @brief A class that handles the ISO11783/J1939 transport protocol. /// @details This class handles transmission and reception of CAN messages up to 1785 bytes. /// Both broadcast and connection mode are supported. Simply call `CANNetworkManager::send_can_message()` /// with an appropriate data length, and the protocol will be automatically selected to be used. - /// @note The use of broadcast messages is discouraged, as it has profound + /// @note The use of multi-frame broadcast messages (BAM) is discouraged, as it has profound /// packet timing implications for your application, and is limited to only 1 active session at a time. /// That session could be busy if you are using DM1 or any other BAM protocol, causing intermittent /// transmit failures from this class. This is not a bug, rather a limitation of the protocol /// definition. - //================================================================================================ class TransportProtocolManager { public: @@ -63,11 +60,7 @@ namespace isobus AnyOtherError = 250 ///< Any reason not defined in the standard }; - //================================================================================================ - /// @class TransportProtocolSession - /// /// @brief A storage object to keep track of session information internally - //================================================================================================ class TransportProtocolSession : public TransportProtocolSessionBase { public: @@ -164,11 +157,11 @@ namespace isobus std::uint8_t clearToSendPacketCountMax = 0xFF; ///< The max packets that can be sent per CTS as indicated by the RTS message }; - static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 16; ///< TP.CM_RTS Multiplexor - static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 17; ///< TP.CM_CTS Multiplexor - static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 19; ///< TP.CM_EOM_ACK Multiplexor - static constexpr std::uint32_t BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR = 32; ///< TP.BAM Multiplexor - static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 255; ///< Abort multiplexor + static constexpr std::uint32_t REQUEST_TO_SEND_MULTIPLEXOR = 0x10; ///< (16) TP.CM_RTS Multiplexor + static constexpr std::uint32_t CLEAR_TO_SEND_MULTIPLEXOR = 0x11; ///< (17) TP.CM_CTS Multiplexor + static constexpr std::uint32_t END_OF_MESSAGE_ACKNOWLEDGE_MULTIPLEXOR = 0x13; ///< (19) TP.CM_EOM_ACK Multiplexor + static constexpr std::uint32_t BROADCAST_ANNOUNCE_MESSAGE_MULTIPLEXOR = 0x20; ///< (32) TP.BAM Multiplexor + static constexpr std::uint32_t CONNECTION_ABORT_MULTIPLEXOR = 0xFF; ///< (255) Abort multiplexor static constexpr std::uint32_t MAX_PROTOCOL_DATA_LENGTH = 1785; ///< The max number of bytes that this protocol can transfer static constexpr std::uint16_t T1_TIMEOUT_MS = 750; ///< The t1 timeout as defined by the standard static constexpr std::uint16_t T2_T3_TIMEOUT_MS = 1250; ///< The t2/t3 timeouts as defined by the standard diff --git a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp index f2fd693d..52491253 100644 --- a/isobus/include/isobus/isobus/can_transport_protocol_base.hpp +++ b/isobus/include/isobus/isobus/can_transport_protocol_base.hpp @@ -16,11 +16,7 @@ namespace isobus { - //================================================================================================ - /// @class TransportProtocolSessionBase - /// /// @brief An object to keep track of session information internally - //================================================================================================ class TransportProtocolSessionBase { public: @@ -92,7 +88,7 @@ namespace isobus virtual std::uint32_t get_total_bytes_transferred() const = 0; /// @brief Get the percentage of bytes that have been sent or received in this session - /// @return The percentage of bytes that have been sent or received (between 0 and 1) + /// @return The percentage of bytes that have been sent or received (between 0 and 100) float get_percentage_bytes_transferred() const; /// @brief Get the control function that is sending the message diff --git a/isobus/src/can_extended_transport_protocol.cpp b/isobus/src/can_extended_transport_protocol.cpp index 64c3a1f7..936ae32e 100644 --- a/isobus/src/can_extended_transport_protocol.cpp +++ b/isobus/src/can_extended_transport_protocol.cpp @@ -165,6 +165,9 @@ namespace isobus nullptr, // No callback nullptr); + // Request the maximum number of packets per DPO via the CTS message + newSession->set_cts_number_of_packet_limit(configuration->get_number_of_packets_per_dpo_message()); + newSession->set_state(StateMachineState::SendClearToSend); activeSessions.push_back(newSession); CANStackLogger::debug("[ETP]: New rx session for 0x%05X. Source: %hu, destination: %hu", parameterGroupNumber, source->get_address(), destination->get_address()); @@ -483,7 +486,7 @@ namespace isobus void ExtendedTransportProtocolManager::process_message(const CANMessage &message) { // TODO: Allow sniffing of messages to all addresses, not just the ones we normally listen to (#297) - if (message.has_valid_source_control_function() && message.has_valid_destination_control_function()) + if (message.has_valid_source_control_function() && message.is_destination_our_device()) { switch (message.get_identifier().get_parameter_group_number()) { @@ -699,7 +702,7 @@ namespace isobus { if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { - CANStackLogger::error("[ETP]: Timeout for destination-specific rx session (expected sequencial data frame)"); + CANStackLogger::error("[ETP]: Timeout for destination-specific rx session (expected sequential data frame)"); abort_session(session, ConnectionAbortReason::Timeout); } } @@ -830,10 +833,10 @@ namespace isobus { packetsThisSegment = session->get_cts_number_of_packet_limit(); } - else if (packetsThisSegment > 16) + if (packetsThisSegment > configuration->get_number_of_packets_per_dpo_message()) { - //! @todo apply CTS number of packets recommendation of 16 via a configuration option - packetsThisSegment = 16; + CANStackLogger::debug("[TP]: Received Request To Send (RTS) with a CTS packet count of %hu, which is greater than the configured maximum of %hu, using the configured maximum instead.", packetsThisSegment, configuration->get_number_of_packets_per_dpo_message()); + packetsThisSegment = configuration->get_number_of_packets_per_dpo_message(); } const std::array buffer{ diff --git a/isobus/src/can_message_data.cpp b/isobus/src/can_message_data.cpp index 050d3abb..fd411ee5 100644 --- a/isobus/src/can_message_data.cpp +++ b/isobus/src/can_message_data.cpp @@ -99,9 +99,15 @@ namespace isobus std::uint8_t CANMessageDataCallback::get_byte(std::size_t index) { - if (index >= dataOffset + bufferSize) + if (index >= totalSize) { - dataOffset += bufferSize; + return 0; + } + + if ((index >= dataOffset + bufferSize) || (index < dataOffset) || (!initialized)) + { + initialized = true; + dataOffset = index; callback(0, dataOffset, std::min(totalSize - dataOffset, bufferSize), buffer.data(), parentPointer); } return buffer[index - dataOffset]; @@ -113,4 +119,4 @@ namespace isobus return self; } -} // namespace isobus \ No newline at end of file +} // namespace isobus diff --git a/isobus/src/can_network_configuration.cpp b/isobus/src/can_network_configuration.cpp index 9dcea874..94ee390e 100644 --- a/isobus/src/can_network_configuration.cpp +++ b/isobus/src/can_network_configuration.cpp @@ -38,14 +38,14 @@ namespace isobus return minimumTimeBetweenTransportProtocolBAMFrames; } - void CANNetworkConfiguration::set_max_number_of_etp_frames_per_edpo(std::uint8_t numberFrames) + void CANNetworkConfiguration::set_number_of_packets_per_dpo_message(std::uint8_t numberFrames) { - extendedTransportProtocolMaxNumberOfFramesPerEDPO = numberFrames; + numberOfPacketsPerDPOMessage = numberFrames; } - std::uint8_t CANNetworkConfiguration::get_max_number_of_etp_frames_per_edpo() const + std::uint8_t CANNetworkConfiguration::get_number_of_packets_per_dpo_message() const { - return extendedTransportProtocolMaxNumberOfFramesPerEDPO; + return numberOfPacketsPerDPOMessage; } void CANNetworkConfiguration::set_max_number_of_network_manager_protocol_frames_per_update(std::uint8_t numberFrames) diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index c8bdf5fc..5de9d20e 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -539,7 +539,7 @@ namespace isobus void TransportProtocolManager::process_message(const CANMessage &message) { // TODO: Allow sniffing of messages to all addresses, not just the ones we normally listen to (#297) - if (message.has_valid_source_control_function() && (message.has_valid_destination_control_function() || message.is_broadcast())) + if (message.has_valid_source_control_function() && (message.is_destination_our_device() || message.is_broadcast())) { switch (message.get_identifier().get_parameter_group_number()) { @@ -792,10 +792,10 @@ namespace isobus } else { - // Waiting on sequencial data frames + // Waiting on sequential data frames if (session->get_time_since_last_update() > T1_TIMEOUT_MS) { - CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequencial data frame)"); + CANStackLogger::error("[TP]: Timeout for destination-specific rx session (expected sequential data frame)"); abort_session(session, ConnectionAbortReason::Timeout); } } diff --git a/isobus/src/can_transport_protocol_base.cpp b/isobus/src/can_transport_protocol_base.cpp index 1b7d2078..843c31f8 100644 --- a/isobus/src/can_transport_protocol_base.cpp +++ b/isobus/src/can_transport_protocol_base.cpp @@ -64,7 +64,11 @@ namespace isobus float TransportProtocolSessionBase::get_percentage_bytes_transferred() const { - return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()); + if (0 == get_message_length()) + { + return 0.0f; + } + return static_cast(get_total_bytes_transferred()) / static_cast(get_message_length()) * 100.0f; } std::shared_ptr TransportProtocolSessionBase::get_destination() const @@ -99,5 +103,4 @@ namespace isobus parent); } } - -} \ No newline at end of file +} diff --git a/test/helpers/control_function_helpers.cpp b/test/helpers/control_function_helpers.cpp index a3f94fe3..85b41400 100644 --- a/test/helpers/control_function_helpers.cpp +++ b/test/helpers/control_function_helpers.cpp @@ -134,4 +134,20 @@ namespace test_helpers return std::make_shared(NAME(0), address, 0); } + class WrappedInternalControlFunction : public InternalControlFunction + { + public: + WrappedInternalControlFunction(NAME name, std::uint8_t address, std::uint8_t canPort) : + InternalControlFunction(name, address, canPort, {}) + { + // We need to set the address manually, since there won't be an address claim state machine running + ControlFunction::address = address; + } + }; + + std::shared_ptr create_mock_internal_control_function(std::uint8_t address) + { + return std::make_shared(NAME(0), address, 0); + } + }; // namespace test_helpers diff --git a/test/helpers/control_function_helpers.hpp b/test/helpers/control_function_helpers.hpp index 08602b20..55e7ac93 100644 --- a/test/helpers/control_function_helpers.hpp +++ b/test/helpers/control_function_helpers.hpp @@ -12,6 +12,8 @@ namespace test_helpers std::shared_ptr create_mock_control_function(std::uint8_t address); + std::shared_ptr create_mock_internal_control_function(std::uint8_t address); + }; // namespace test_helpers #endif // CONTROL_FUNCTION_HELPERS_HPP diff --git a/test/transport_protocol_tests.cpp b/test/transport_protocol_tests.cpp index 5dd469c0..f74da19f 100644 --- a/test/transport_protocol_tests.cpp +++ b/test/transport_protocol_tests.cpp @@ -363,7 +363,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMessageSending) { constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; - auto originator = test_helpers::create_mock_control_function(0x01); + auto originator = test_helpers::create_mock_internal_control_function(0x01); auto receiver = test_helpers::create_mock_control_function(0x02); std::deque responseQueue; @@ -661,7 +661,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMessageReceiving) constexpr std::array dataToReceive = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; auto originator = test_helpers::create_mock_control_function(0x01); - auto receiver = test_helpers::create_mock_control_function(0x02); + auto receiver = test_helpers::create_mock_internal_control_function(0x02); std::uint8_t messageCount = 0; auto receiveMessageCallback = [&](const CANMessage &message) { @@ -876,8 +876,8 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificTimeoutInitiation) { constexpr std::array dataToTransfer = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; - auto originator = test_helpers::create_mock_control_function(0x01); - auto receiver = test_helpers::create_mock_control_function(0x02); + auto originator = test_helpers::create_mock_internal_control_function(0x01); + auto receiver = test_helpers::create_mock_internal_control_function(0x02); std::deque originatorQueue; std::deque receiverQueue; @@ -984,8 +984,8 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificTimeoutCompletion) { constexpr std::array dataToTransfer = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; - auto originator = test_helpers::create_mock_control_function(0x01); - auto receiver = test_helpers::create_mock_control_function(0x02); + auto originator = test_helpers::create_mock_internal_control_function(0x01); + auto receiver = test_helpers::create_mock_internal_control_function(0x02); std::deque originatorQueue; std::deque receiverQueue; @@ -1119,19 +1119,19 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificConcurrentMessaging) constexpr std::array dataToReceive1 = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; constexpr std::array dataToReceive2 = { 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }; - auto originator1 = test_helpers::create_mock_control_function(0x01); // Send pgn1ToReceive, dataToReceive1 - auto originator2 = test_helpers::create_mock_control_function(0x02); // Send pgn1ToReceive, dataToReceive1 - auto originator3 = test_helpers::create_mock_control_function(0x03); // Send pgn1ToReceive, dataToReceive2 - auto originator4 = test_helpers::create_mock_control_function(0x04); // Send pgn2ToReceive, dataToReceive1 - auto originator5 = test_helpers::create_mock_control_function(0x05); // Send pgn2ToReceive, dataToReceive2 - auto convergingReceiver = test_helpers::create_mock_control_function(0x07); - - auto divergingOriginator = test_helpers::create_mock_control_function(0x06); - auto receiver1 = test_helpers::create_mock_control_function(0x08); // Receive pgn1ToReceive, dataToReceive1 - auto receiver2 = test_helpers::create_mock_control_function(0x09); // Receive pgn1ToReceive, dataToReceive1 - auto receiver3 = test_helpers::create_mock_control_function(0x0A); // Receive pgn1ToReceive, dataToReceive2 - auto receiver4 = test_helpers::create_mock_control_function(0x0B); // Receive pgn2ToReceive, dataToReceive1 - auto receiver5 = test_helpers::create_mock_control_function(0x0C); // Receive pgn2ToReceive, dataToReceive2 + auto originator1 = test_helpers::create_mock_internal_control_function(0x01); // Send pgn1ToReceive, dataToReceive1 + auto originator2 = test_helpers::create_mock_internal_control_function(0x02); // Send pgn1ToReceive, dataToReceive1 + auto originator3 = test_helpers::create_mock_internal_control_function(0x03); // Send pgn1ToReceive, dataToReceive2 + auto originator4 = test_helpers::create_mock_internal_control_function(0x04); // Send pgn2ToReceive, dataToReceive1 + auto originator5 = test_helpers::create_mock_internal_control_function(0x05); // Send pgn2ToReceive, dataToReceive2 + auto convergingReceiver = test_helpers::create_mock_internal_control_function(0x07); + + auto divergingOriginator = test_helpers::create_mock_internal_control_function(0x06); + auto receiver1 = test_helpers::create_mock_internal_control_function(0x08); // Receive pgn1ToReceive, dataToReceive1 + auto receiver2 = test_helpers::create_mock_internal_control_function(0x09); // Receive pgn1ToReceive, dataToReceive1 + auto receiver3 = test_helpers::create_mock_internal_control_function(0x0A); // Receive pgn1ToReceive, dataToReceive2 + auto receiver4 = test_helpers::create_mock_internal_control_function(0x0B); // Receive pgn2ToReceive, dataToReceive1 + auto receiver5 = test_helpers::create_mock_internal_control_function(0x0C); // Receive pgn2ToReceive, dataToReceive2 std::deque originatingQueue; std::deque receivingQueue; @@ -1375,8 +1375,8 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificAndBroadcastMessageConcurrent) constexpr std::array dataToReceiveBroadcast = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11 }; constexpr std::array dataToReceiveSpecific = { 0xAC, 0xAB, 0xAA, 0xA9, 0xA8, 0xA7, 0xA6, 0xA5, 0xA4, 0xA3, 0xA2, 0xA1 }; - auto originator = test_helpers::create_mock_control_function(0x01); - auto receiver = test_helpers::create_mock_control_function(0x02); + auto originator = test_helpers::create_mock_internal_control_function(0x01); + auto receiver = test_helpers::create_mock_internal_control_function(0x02); std::deque originatingQueue; std::deque receivingQueue; @@ -1511,7 +1511,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificAbortInitiation) { constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; - auto originator = test_helpers::create_mock_control_function(0x01); + auto originator = test_helpers::create_mock_internal_control_function(0x01); auto receiver = test_helpers::create_mock_control_function(0x02); std::deque responseQueue; @@ -1596,7 +1596,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificMultipleCTS) { constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 }; - auto originator = test_helpers::create_mock_control_function(0x01); + auto originator = test_helpers::create_mock_internal_control_function(0x01); auto receiver = test_helpers::create_mock_control_function(0x02); std::deque responseQueue; @@ -1698,8 +1698,8 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificRandomCTS) { constexpr std::array dataToSent = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }; - auto originator = test_helpers::create_mock_control_function(0x01); - auto receiver = test_helpers::create_mock_control_function(0x02); + auto originator = test_helpers::create_mock_internal_control_function(0x01); + auto receiver = test_helpers::create_mock_internal_control_function(0x02); auto randomControlFunction = test_helpers::create_mock_control_function(0x03); std::deque originatorQueue; std::deque receiverQueue; @@ -1814,7 +1814,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificRejectForOutOfResources) { auto originator1 = test_helpers::create_mock_control_function(0x01); auto originator2 = test_helpers::create_mock_control_function(0x02); - auto receiver = test_helpers::create_mock_control_function(0x0B); + auto receiver = test_helpers::create_mock_internal_control_function(0x0B); bool originator1CTSReceived = false; bool originator2AbortReceived = false; @@ -1920,7 +1920,7 @@ TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificRejectForOutOfResources) TEST(TRANSPORT_PROTOCOL_TESTS, DestinationSpecificOverwriteSession) { auto originator = test_helpers::create_mock_control_function(0x01); - auto receiver = test_helpers::create_mock_control_function(0x0B); + auto receiver = test_helpers::create_mock_internal_control_function(0x0B); std::size_t messageCount = 0; auto receiveMessageCallback = [&](const CANMessage &message) { From be1cb56730f0151b00f3add80a41247ce75f6b4a Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Mon, 15 Jan 2024 13:39:30 +0100 Subject: [PATCH 8/9] fix(core): incorrect buffer allocation for data message callback --- isobus/src/can_message_data.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/isobus/src/can_message_data.cpp b/isobus/src/can_message_data.cpp index fd411ee5..24138efb 100644 --- a/isobus/src/can_message_data.cpp +++ b/isobus/src/can_message_data.cpp @@ -87,9 +87,9 @@ namespace isobus totalSize(size), callback(callback), parentPointer(parentPointer), + buffer(chunkSize), bufferSize(chunkSize) { - buffer.reserve(bufferSize); } std::size_t CANMessageDataCallback::size() const From 810c35bfad4acdd4fb12971641a4d00a9398d5b3 Mon Sep 17 00:00:00 2001 From: Daan Steenbergen Date: Tue, 16 Jan 2024 20:09:29 +0100 Subject: [PATCH 9/9] fix(example): use percentage i.s.o. fraction for progress bar in transport layer example --- examples/transport_layer/main.cpp | 6 +++--- isobus/src/can_transport_protocol.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/transport_layer/main.cpp b/examples/transport_layer/main.cpp index 17a5aaae..0a71eb21 100644 --- a/examples/transport_layer/main.cpp +++ b/examples/transport_layer/main.cpp @@ -201,11 +201,11 @@ void print_progress_bar(const std::shared_ptr sess std::cout << "["; for (std::uint8_t i = 0; i < width; i++) { - if (i < static_cast(percentage * width)) + if (i < static_cast(percentage / 100 * width)) { std::cout << "="; } - else if (i == static_cast(percentage * width)) + else if (i == static_cast(percentage / 100 * width)) { std::cout << ">"; } @@ -214,6 +214,6 @@ void print_progress_bar(const std::shared_ptr sess std::cout << " "; } } - std::cout << "] " << static_cast(percentage * 100.0f) << "% (" << session->get_total_bytes_transferred() << "/" << session->get_message_length() << " bytes)\r"; + std::cout << "] " << static_cast(percentage) << "% (" << session->get_total_bytes_transferred() << "/" << session->get_message_length() << " bytes)\r"; std::cout.flush(); } diff --git a/isobus/src/can_transport_protocol.cpp b/isobus/src/can_transport_protocol.cpp index 5de9d20e..59b70345 100644 --- a/isobus/src/can_transport_protocol.cpp +++ b/isobus/src/can_transport_protocol.cpp @@ -698,7 +698,7 @@ namespace isobus session->set_state(StateMachineState::WaitForEndOfMessageAcknowledge); } } - else if (session->get_cts_number_of_packets_remaining() == 0) + else if ((session->get_cts_number_of_packets_remaining() == 0) && !session->is_broadcast()) { session->set_state(StateMachineState::WaitForClearToSend); }