/*! * * Copyright (c) 2023 Diality Inc. - All Rights Reserved. * * \copyright * THIS CODE MAY NOT BE COPIED OR REPRODUCED IN ANY FORM, IN PART OR IN * WHOLE, WITHOUT THE EXPLICIT PERMISSION OF THE COPYRIGHT OWNER. * * \file MsgLink.h * \author (last) Phil Braica * \date (last) 23-Jan-2023 * \author (original) Phil Braica * \date (original) 23-Jan-2023 * */ #ifndef MSG_LINK_H_ #define MSG_LINK_H_ #include "EventWait.h" #include "UpdateProtocol.h" #include "Logger.h" #include "CanInterface.h" #include #ifdef __cplusplus extern "C" { #endif // Send call. extern bool sendPacketFromUi(uint16 msgId, uint8* pData, uint32 length); #ifdef __cplusplus } #endif namespace SwUpdate { /*! * Heuristic tuned value, typically it is 2x or more the wire * rate for most CAN systems. * * /note This is a tunable heuristic. */ const float BandwidthFactor = 2.0f; /*! * The total amount of payload bytes to round trip data + response. */ const uint32 TotalRoundTripDataSizeBytes = (sizeof(SwUpdateDataBuffer) + sizeof(SwUpdateResponse)); /*! * For each 8 bytes of payload, 114 wire bits are * used (header + payload + tail). */ const float BytesToCanBits = 114.0f / 8.0f; /*! * Pause after a NACK before sending reset and retry. */ const std::chrono::duration g_ms_pause(20); /*! * \brief Convert ACK value to milliseconds. * * 0=NACK 1= ACK no wait, else ms = (ack-1)*scale. */ const uint32 gAckSleepScale = 200; /*! * This is 2x the line speed transfer of a * message relative to kbps of the line in milliseconds. * * Example: * (sizeof(SwUpdateDataBuffer) + sizeof(SwUpdateResponse))/8) = 34. * 34 packets * 114 bits per packet = 3,876 bits. * / 250kpbs = 15.504 ms if full bandwidth for 250kbps. * * 2 for collisions/normal stuff = * Expected round trip within 31 ms else we should retry. * */ const float RetryKbpsScaleFactor = TotalRoundTripDataSizeBytes * BytesToCanBits * BandwidthFactor; /*! * \brief Send a command and wait for response. * * Typical use is wait on bool sendOk(...), and if it failed then * the effort timed out for retries. * * The numberRetries() call checks for too much cumulative failures * which indicates the bus is improperly being used by other nodes * while attempting the software update or cyber attack. */ class MsgLink { protected: /*! * \brief A Tiny statemachine like enum class. * * Protected instead of private to aid testing. * * None - In this state a retry method is used. * Retries are not time based per-say as this is an * embedded processor, a certain count of retries * must expire. NACKs in this mode are just a reason * to retry, just as no response is a reason to retry. * * Waiting - In this state, an ACK with a value > 1 * was received while in "None" state. This means * the previous message was received and an * attempt to do that work is in progres but it * will take a while. It is expected to take * upto (ackValue-1)*200ms. * if it times out: * likely something bad enough occured that * a user wants to be involved. * if an ack is received: go to the next step now. * if a nack is received: treat as a timeout. * * Success / Failure - We were in "waiting" and got * an Ack / Nack respectively. * * The "timout" value can't be set multiple times * for one incoming message and this prevents anything * in FW being capable of making the UI update looking * like it got locked up. */ enum class TimeoutEnum { None = 0, ///< Not using timeout. Waiting = 1, ///< Waiting till next ack/nack or timeout. Success = 2, ///< While waiting got Ack. Failure = 3 ///< While waiting got Nack. }; public: /*! * \brief Constructor. */ MsgLink(float kbps) : _ew((uint32)(60 + (RetryKbpsScaleFactor / kbps))), _expecting(false), _retries(0), _expectedId(0), _msgIdSlot(0), _timeoutMode(TimeoutEnum::None), _wasData(false), _dataBuf({0,0,0,0, {0}}) { } /*! * \brief Virtual destructor. */ virtual ~MsgLink() { _expecting = false; } /*! * \brief Get the number of message retries. */ uint32 numberRetries() { return _retries; } /*! * \brief Reset the number of retries. */ void resetRetries() { _retries = 0; } /*! * \brief Abort waiting. */ void abort() { _expecting = false; _ew.fireEvent(); } /*! * \brief Send a message, wait as needed for retry/response. * * \param msgId The message * \param msg The message bytes, already with security. * \param maxEffort Maximum number of 1ms retries. * * \return True on success, False on retry failed or timeout. */ bool sendOk( uint16 msgId, uint8 * msg, uint16 maxEffort) { // We are expecting data. _expecting = true; _wasData = (msgId == SwUpdateCanMsgIds::DataBufferId_HD_FW) || (msgId == SwUpdateCanMsgIds::DataBufferId_HD_FPGA) || (msgId == SwUpdateCanMsgIds::DataBufferId_DG_FW) || (msgId == SwUpdateCanMsgIds::DataBufferId_DG_FPGA); // Either command or data. if data use ResponseId, // if a command and verify or version use VerifyId else ResponseId. _expectedId = _wasData ? SwUpdateCanMsgIds::ResponseId : SwUpdate_getCmd(msg[1]) == SwUpdateCmdEnum::Verify ? SwUpdateCanMsgIds::VerifyId : SwUpdate_getCmd(msg[1]) == SwUpdateCmdEnum::Version ? SwUpdateCanMsgIds::VerifyId : ResponseId; _msgIdSlot = msg[0]; // For ack/nack matching. // Sending a command (8 bytes) or data (256). uint32 length = _wasData ? sizeof(SwUpdateDataBuffer) : sizeof(SwUpdateCommand); if (_wasData) { memcpy(&_dataBuf, msg, sizeof(SwUpdateDataBuffer)); } // _timeoutMode: // == 0 - Normal use. // 1 - Wait till time. // 2 - Was waiting for timeout but got an ACK. // 3 - Was waiting for timeout but got a NACK. _timeoutMode = TimeoutEnum::None; for (uint32 effortSpent = 0; // When None, keep going till maxEffort, when not none, it's event based. (effortSpent < maxEffort) || (TimeoutEnum::None != _timeoutMode); effortSpent++) { // No longer expecting! if (!_expecting) { // Cancel, etc. break; } _ew.re_arm(); // If in timeout mode we're waiting for an ack/nack or time to expire. // We got an ack so we know the message was received, but we're not to // continue till we another ack/nack or treat timeout as fail. if (TimeoutEnum::None == _timeoutMode) { // Call indirectly to make this more testable. this->sendPacket(msgId, msg, length); } // Wait to either retry or success. if (_ew.wait()) { if (TimeoutEnum::Waiting == _timeoutMode) { continue; } // If we got a NACK while timing out, abort this so the user can // note that a long thing failed and can feel they are in control of // retrying something that was significant. // // Otherwise return true. _expecting = false; return (TimeoutEnum::Failure != _timeoutMode) ? true : false; } // No retries in _timeoutMode. if (TimeoutEnum::None == _timeoutMode) { _retries++; } else { // Just keep waiting? const auto timeNow = std::chrono::steady_clock::now(); if (timeNow > _timeout) { // We timed out. Fail. return false; } continue; } // If we're still expecting then we do a retry. // Before retry send a command that should clear us. if (_expecting && _wasData) { SwUpdateCommand resync; resync.id = _msgIdSlot + 1; SwUpdateTargetEnum target = (SwUpdateTargetEnum)(((SwUpdateDataBuffer *)msg)->kind); resync.cmd = SwUpdate_FormCommand(target, SwUpdateCmdEnum::Resync); resync.rand = 0; SwUpdate_createSecurity((uint8 *)&resync, sizeof(SwUpdateCommand)); std::this_thread::sleep_for(g_ms_pause); this->sendPacket(SwUpdateCanMsgIds::CommandId, (uint8*)&resync, 8); std::this_thread::sleep_for(g_ms_pause); } } _expecting = false; return false; } /*! * \brief Send a packet. * * \param mstId Messge Id. * \param pData Message data. * \param length Message length. */ void sendPacket(uint16 msgId, uint8* pData, uint32 length) { _CanInterface.sendSWUpdateMsg(msgId, pData, length); } /*! * \brief Receive and check if matched. * * \param msgId Can message ID. * \param pData Data pointer. * * \return True if we received the expected message. */ bool checkReceived(uint16 msgId, uint8 * pData) { bool rv = false; if (_expecting) { if (_wasData) { rv = SwUpdate_checkSecurityForData(pData, (uint8*)&_dataBuf); } else { if ((SwUpdate_verifySecurity(pData, sizeof(SwUpdateResponse))) && (msgId == _expectedId)) { // msgId resp->id is ack | rv // is resp match // F * * | T Its a verify/version // T F * | F Not for this thread // T T F | F For the thread not an ack. // T T T | T For this thread and an ack. if (msgId == SwUpdateCanMsgIds::ResponseId) { SwUpdateResponse *resp = (SwUpdateResponse *)pData; if (resp->id == _msgIdSlot) { // _timeoutMode ackNack | rv _timeout _timeoutMode // None 0 | F // None 1 | T // None >1 | T calc. 1 // !None 0 | T 3 // !None 1 | T 2 // !None >1 | T if (TimeoutEnum::None == _timeoutMode) { if (resp->ackNack >= 1) { rv = true; if (resp->ackNack > 1) { // Only set the timeout once. // This way a bug in the FW code that sends us replies // can't put it this an infinite stall (which would have to be aborted by a user). _timeout = std::chrono::steady_clock::now() + std::chrono::duration((resp->ackNack-1) * gAckSleepScale); _timeoutMode = TimeoutEnum::Waiting; } } } else { // _timeoutMode > 0 rv = true; _timeoutMode = resp->ackNack == 0 ? TimeoutEnum::Failure : resp->ackNack == 1 ? TimeoutEnum::Success : TimeoutEnum::Waiting; } } } else { rv = true; } } } // It's good? fire event. if (rv) { _ew.fireEvent(); } } return rv; } protected: EventWait _ew; ///< Wait for time or event. bool _expecting; ///< Expecting data. uint32 _retries; ///< Total number of retries. uint16 _expectedId; ///< Expected message Id. uint8 _msgIdSlot; ///< Message slot. TimeoutEnum _timeoutMode; ///< Timeout enum. std::chrono::time_point _timeout; ///< Timeout expire time. bool _wasData; ///< Was data. SwUpdateDataBuffer _dataBuf; ///< Data buffer. }; } // namespace SwUpdate #endif // MSG_LINK_H_