403 lines
14 KiB
C++
403 lines
14 KiB
C++
/*******************************************************************************
|
|
* 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 <map>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <chrono>
|
|
#include <deque>
|
|
|
|
#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<numReceptionBlocks; ++i) {
|
|
if (!isBlockDone(i)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
bool anyPayloadReceived() {
|
|
for (int i=0; i<numReceptionBlocks; ++i) {
|
|
if (blockReceiveOffsets[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<MissingReceiveSegment> missingReceiveSegments;
|
|
std::deque<std::pair<int, int> > 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<unsigned char, AlignedAllocator<unsigned char> > receiveBuffer;
|
|
std::vector<unsigned char, AlignedAllocator<unsigned char> > blockReceiveBuffers[MAX_DATA_BLOCKS];
|
|
int blockReceiveOffsets[MAX_DATA_BLOCKS];
|
|
int blockReceiveSize[MAX_DATA_BLOCKS];
|
|
int blockValidSize[MAX_DATA_BLOCKS];
|
|
std::vector<unsigned char> 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
|