/******************************************************************************* * Copyright (c) 2021 Nerian Vision GmbH * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. *******************************************************************************/ #ifndef VISIONTRANSFER_DATABLOCKPROTOCOL_H #define VISIONTRANSFER_DATABLOCKPROTOCOL_H #include #include #include #include #include #include "visiontransfer/alignedallocator.h" #include "visiontransfer/exceptions.h" namespace visiontransfer { namespace internal { /** * \brief A protocol for transmitting large blocks of data over a network. * * The protocol slices the large data block into several smaller chunks * that can be transmitted over a network. A user defined header is * always transmitted before this large data block. * * There are two different implementations for UDP and TCP. In UDP mode, * packet loss is handled by performing a packet re-transmission. In TCP * mode, data does not have to be received with the same packet size as * it is sent out. * * This class is intended to be used by ImageProtocol and should normally * not be used directly. */ class DataBlockProtocol { public: enum ProtocolType { PROTOCOL_TCP, PROTOCOL_UDP }; // static const int MAX_DATA_BLOCKS = 8; // Constants that are also used in other places. static const int MAX_TCP_BYTES_TRANSFER = 0xFFFF; //64K - 1 static const int MAX_UDP_RECEPTION = 0x4000; //16K static const int MAX_OUTSTANDING_BYTES = 2*MAX_TCP_BYTES_TRANSFER; #pragma pack(push,1) // Extends previous one-channel 6-byte raw header buffer // Legacy transfers can be detected via non-zero netTransferSizeDummy struct HeaderPreamble { uint16_t netHeaderSize; int32_t netTransferSizeDummy; // layout compatibility, legacy detection uint32_t netTransferSizes[MAX_DATA_BLOCKS]; // per-block total size }; struct SegmentHeaderUDP { uint32_t segmentOffset; }; struct SegmentHeaderTCP { uint32_t fragmentSize; uint32_t segmentOffset; }; #pragma pack(pop) /** * \brief Creates a new instance * * \param server If set to true, this object will be a communication server. * \param protType The network transport protocol that is used. * \param maxUdpPacketSize Maximum allowed size of a UDP packet when sending data. */ DataBlockProtocol(bool server, ProtocolType protType, int maxUdpPacketSize); /** * \brief Returns the size of the overhead data that is required for * transferring a single network message. */ int getProtocolOverhead() const { return protType == PROTOCOL_UDP ? sizeof(int) : 0; } /** * \brief Returns the maximum payload size that can be received */ int getMaxReceptionSize() const; /** * \brief Resets all transfer related internal variables */ void resetTransfer(); /** * \brief Sets a user-defined header that shall be transmitted with * the next transfer * * \param data Pointer to the data of the header that should be * transferred. * \param headerSize Size of the data in \c data. * \param transferSize Total size of the payload for the next transfer. * * This method must be called before setTransferData(). A call before * the start of each transfer is necessary. There must be at least * 6 additional bytes of reserved memory after the end and before the * beginning of \c data. */ void setTransferHeader(unsigned char* data, int headerSize, int blocks); /** * \brief Sets the per-block transfer size * * \param bytes Size of the data pointed to with the matching setTransferData() * * Replaces the old single-buffer total size that was prepended to the * second-level header alongside the header size. */ void setTransferBytes(int block, long bytes); /** * \brief Sets the payload data for the next transfer. * * \param data Pointer to the data that should be transferred. * \param validBytes The number of bytes that are currently * valid in \c data. * * Part of \c data will be overwritten. There must be at least 4 additional * allocated bytes at the end of \c data. * * If \c validBytes is set to a value smaller than the total transfer * size, only a partial transfer is performed. Subsequent calls to * setTransferValidBytes() are then necessary. */ void setTransferData(int block, unsigned char* data, int validBytes = 0x7FFFFFFF); /** * \brief Updates the number of valid bytes in a partial transfer. * * \param validBytes The number of already valid bytes in the previously * set data pointer. * * This method has to be called whenever new data is available in a * partial transfer. \see setTransferData() */ void setTransferValidBytes(int block, int validBytes); /** * \brief Gets the next network message for the current transfer. * * \param length The length of the network message. * \return Pointer to the network message data. * * If the transfer has already been completed or if there are currently * no more valid bytes to be transmitted, a null pointer is returned. */ const unsigned char* getTransferMessage(int& length); /** * \brief Returns true if the current transfer has been completed. */ bool transferComplete(); /** * \brief Gets a buffer for receiving the next network message. * * \param maxLength The expected maximum length that is required for * receiving a network message. * * The returned buffer is a subsection of the internal receive buffer. */ unsigned char* getNextReceiveBuffer(int maxLength); /** * \brief Resets the message reception. * * \param dropped If true, then this reset is rated as an error and * internal counter for dropped transfers is increased. */ void resetReception(bool dropped); /** * \brief Handles a received network message. * * \param length Length of the received network message. * \param transferComplete Set to true if a new transfer is complete after * receiving the current packet * * Please see ImageProtocol::processReceivedMessage() for further details. */ void processReceivedMessage(int length, bool& transferComplete); /** * \brief Returns the data that has been received for the current transfer. * * \param length Will be set to the number of bytes that have been received. * \return Pointer to the buffer containing the received data. * * The received data is valid until receiving the first network * message for a new transfer. */ unsigned char* getReceivedData(int& length); /** * \brief Returns the header data that has been received for the * current transfer. * * \param length Will be set to the length of the header data in * bytes. * \return Pointer to the buffer containing the received header data. * * The received header data is valid until receiving the first network * message for a new transfer. */ unsigned char* getReceivedHeader(int& length); /** * \brief Returns the internal counter of dropped transfers during * reception. */ int getDroppedReceptions() const { return droppedReceptions; } /** * \brief Returns true if the last network message has established a * new connection from a client * * For TCP this method always returns false as connections are * handled by the transport protocol. */ bool newClientConnected(); /** * \brief Returns true if a remote connection is established. * * For TCP this method always returns true as connections are * handled by the transport protocol. */ bool isConnected() const; /** * \brief If a control message is pending to be transmitted, then * the message data will be returned by this method. * * \param length Will be set to the length of the message. * \return Pointer to the message data or NULL if no message is pending. * * Control messages are only used if the UDP transfer protocol is * selected. For TCP this method always returns a null pointer. */ const unsigned char* getNextControlMessage(int& length); unsigned char* getBlockReceiveBuffer(int block) { if (block >= numReceptionBlocks) { throw ProtocolException("Tried to get receive buffer beyond initialized block range"); } return &blockReceiveBuffers[block][0]; } int getBlockValidSize(int block) { if (block >= numReceptionBlocks) { throw ProtocolException("Tried to get valid buffer index beyond initialized block range"); } return blockValidSize[block]; } bool isBlockDone(int block) { if (block >= numReceptionBlocks) { throw ProtocolException("Tried to get completion status of uninitialized block"); } return blockValidSize[block] >= blockReceiveSize[block]; } bool allBlocksDone() { for (int i=0; i 0) return true; } return false; } std::string statusReport(); bool wasHeaderReceived() const { return headerReceived; } private: // The pimpl idiom is not necessary here, as this class is usually not // used directly struct MissingReceiveSegment { int offset; int length; bool isEof; unsigned char subsequentData[4]; }; static constexpr int HEARTBEAT_INTERVAL_MS = 1000; static constexpr int RECONNECT_TIMEOUT_MS = 1000; static constexpr unsigned char CONNECTION_MESSAGE = 0x01; static constexpr unsigned char CONFIRM_MESSAGE = 0x02; static constexpr unsigned char HEADER_MESSAGE = 0x03; static constexpr unsigned char RESEND_MESSAGE = 0x04; static constexpr unsigned char EOF_MESSAGE = 0x05; static constexpr unsigned char HEARTBEAT_MESSAGE = 0x06; bool isServer; ProtocolType protType; int maxPayloadSize; int minPayloadSize; // Transfer related variables bool transferDone; unsigned char* rawDataArr[MAX_DATA_BLOCKS]; int rawDataArrStrideHackOrig[MAX_DATA_BLOCKS]; int rawDataArrStrideHackRepl[MAX_DATA_BLOCKS]; int rawValidBytes[MAX_DATA_BLOCKS]; int transferOffset[MAX_DATA_BLOCKS]; int transferSize[MAX_DATA_BLOCKS]; char overwrittenTransferData[sizeof(SegmentHeaderTCP)]; int overwrittenTransferIndex; int overwrittenTransferBlock; unsigned char* transferHeaderData; int transferHeaderSize; int totalBytesCompleted; int totalTransferSize; int numTransferBlocks; int lastTransmittedBlock; // Reliability related variables std::deque missingReceiveSegments; std::deque > missingTransferSegments; bool waitingForMissingSegments; int totalReceiveSize; unsigned char controlMessageBuffer[1024]; // Connection related variables bool connectionConfirmed; bool confirmationMessagePending; bool eofMessagePending; bool clientConnectionPending; bool resendMessagePending; std::chrono::steady_clock::time_point lastRemoteHostActivity; std::chrono::steady_clock::time_point lastSentHeartbeat; std::chrono::steady_clock::time_point lastReceivedHeartbeat; // Reception related variables std::vector > receiveBuffer; std::vector > blockReceiveBuffers[MAX_DATA_BLOCKS]; int blockReceiveOffsets[MAX_DATA_BLOCKS]; int blockReceiveSize[MAX_DATA_BLOCKS]; int blockValidSize[MAX_DATA_BLOCKS]; std::vector receivedHeader; bool finishedReception; int droppedReceptions; int completedReceptions; double lostSegmentRate; int lostSegmentBytes; unsigned char unprocessedMsgPart[MAX_OUTSTANDING_BYTES]; int unprocessedMsgLength; bool headerReceived; bool legacyTransfer; int numReceptionBlocks; int receiveOffset; const unsigned char* extractPayload(const unsigned char* data, int& length, bool& error); bool processControlMessage(int length); void restoreTransferBuffer(); bool generateResendRequest(int& length); void getNextTransferSegment(int& block, int& offset, int& length); void parseResendMessage(int length); void parseEofMessage(int length); void integrateMissingUdpSegments(int block, int lastSegmentOffset, int lastSegmentSize); void processReceivedUdpMessage(int length, bool& transferComplete); void processReceivedTcpMessage(int length, bool& transferComplete); void resizeReceiveBuffer(); int parseReceivedHeader(int length, int offset); void zeroStructures(); void splitRawOffset(int rawSegmentOffset, int& dataBlockID, int& segmentOffset); int mergeRawOffset(int dataBlockID, int segmentOffset, int reserved=0); }; }} // namespace #endif