Protocol.h (249 lines of code) (raw):

/** * Copyright (c) 2014-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include <wdt/ErrorCodes.h> #include <wdt/util/EncryptionUtils.h> #include <folly/Range.h> #include <limits.h> #include <stddef.h> #include <string> #include <vector> namespace facebook { namespace wdt { // Note: we use int64_t internally for most things - it helps for arithmetic // and not getting accidental overflow when substracting, it helps comparaison // and also idendtifying errors as negative values. // BUT we made a mistake in early version of wdt where we used an encoding that // doesn't efficiently represent negative values - so in term of serializing // ints on the wire we expect all numbers to actually be positive (which is // more efficient when only positive numbers are indeed encoded) // For future fields where small negative value occurs, do use the I64 functions // without the trailing C for compatibility or use U64 when you know for sure // the data encoded is >= 0 (in util/SerializationUtil.h) /// Checkpoint consists of port number, number of successfully transferred /// blocks and number of bytes received for the last block struct Checkpoint { int32_t port{0}; /// number of complete blocks received int64_t numBlocks{0}; /// Next three fields are only set if a block is received partially /// seq-id of the partially received block (and we don't use encryption /// which doesn't allow using partial blocks as they can't be authenticated) int64_t lastBlockSeqId{0}; // was -1 in 1.26 /// block offset of the partially received block int64_t lastBlockOffset{0}; /// number of bytes received for the partially received block int64_t lastBlockReceivedBytes{0}; bool hasSeqId{false}; Checkpoint() { } explicit Checkpoint(int32_t port) { this->port = port; } bool hasPartialBlockInfo() const { return (hasSeqId && lastBlockSeqId >= 0 && lastBlockReceivedBytes > 0); } void resetLastBlockDetails() { lastBlockReceivedBytes = 0; lastBlockSeqId = 0; // was -1 in 1.26 lastBlockOffset = 0; } void setLastBlockDetails(int64_t seqId, int64_t offset, int64_t receivedBytes) { this->lastBlockSeqId = seqId; this->lastBlockOffset = offset; this->lastBlockReceivedBytes = receivedBytes; } void incrNumBlocks() { numBlocks++; } }; std::ostream &operator<<(std::ostream &os, const Checkpoint &checkpoint); /// structure representing a single chunk of a file struct Interval { /// start offset int64_t start_{0}; /// end offset int64_t end_{0}; Interval() { } Interval(int64_t start, int64_t end) : start_(start), end_(end) { WDT_CHECK(end_ >= start_); } /// @return size of the chunk int64_t size() const { return end_ - start_; } bool operator<(const Interval &chunk) const { return this->start_ < chunk.start_; } bool operator==(const Interval &chunk) const { return this->start_ == chunk.start_ && this->end_ == chunk.end_; } }; /// class representing chunks in a file class FileChunksInfo { public: /// making the object noncopyable FileChunksInfo(const FileChunksInfo &) = delete; FileChunksInfo &operator=(const FileChunksInfo &) = delete; FileChunksInfo(FileChunksInfo &&) = default; FileChunksInfo &operator=(FileChunksInfo &&) = default; FileChunksInfo() { } /** * @param seqId seq-id of the file * @param fileName file-name * @param fileSize file-size */ FileChunksInfo(int64_t seqId, std::string &fileName, int64_t fileSize) : seqId_(seqId), fileName_(fileName), fileSize_(fileSize) { } /// @return file-name const std::string &getFileName() const { return fileName_; } /// @param fileName file-name to be set void setFileName(const std::string &fileName) { fileName_ = fileName; } /// @return seq-id of the file int64_t getSeqId() const { return seqId_; } /// @param seqId seq-id to be set void setSeqId(int64_t seqId) { seqId_ = seqId; } /// @return file-size int64_t getFileSize() const { return fileSize_; } /// @param fileSize file-size to be set void setFileSize(int64_t fileSize) { fileSize_ = fileSize; } /// @return chunks of the file const std::vector<Interval> &getChunks() const { return chunks_; } /// @param chunk chunk to be added void addChunk(const Interval &chunk); /// merges all the chunks void mergeChunks(); int64_t getTotalChunkSize() const; /// @return list of chunks which are not part of the chunks-list std::vector<Interval> getRemainingChunks(int64_t curFileSize); bool operator==(const FileChunksInfo &fileChunksInfo) const { return this->seqId_ == fileChunksInfo.seqId_ && this->fileName_ == fileChunksInfo.fileName_ && this->chunks_ == fileChunksInfo.chunks_ && this->fileSize_ == fileChunksInfo.fileSize_; } friend std::ostream &operator<<(std::ostream &os, const FileChunksInfo &fileChunksInfo); private: /// seq-id of the file int64_t seqId_{0}; /// name of the file std::string fileName_; /// size of the file int64_t fileSize_{0}; /// list of chunk info std::vector<Interval> chunks_; }; /// enum representing file allocation status at the receiver side enum FileAllocationStatus { NOT_EXISTS, // file does not exist EXISTS_CORRECT_SIZE, // file exists with correct size EXISTS_TOO_LARGE, // file exists, but too large EXISTS_TOO_SMALL, // file exists, but too small TO_BE_DELETED, // file not needed, should be deleted }; /// structure representing details of a block struct BlockDetails { /// name of the file std::string fileName; /// sequence-id of the file int64_t seqId{0}; /// size of the file int64_t fileSize{0}; /// offset of the block from the start of the file int64_t offset{0}; /// size of the block int64_t dataSize{0}; /// receiver side file allocation status FileAllocationStatus allocationStatus{NOT_EXISTS}; /// seq-id of previous transfer, only valid if there is a size mismatch int64_t prevSeqId{0}; }; /// structure representing settings cmd struct Settings { /// sender side read timeout int readTimeoutMillis{0}; /// sender side write timeout int writeTimeoutMillis{0}; /// transfer-id std::string transferId{0}; /// whether checksum in enabled or not bool enableChecksum{0}; /// whether sender wants to read previously transferred chunks or not bool sendFileChunks{0}; /// whether block mode is disabled bool blockModeDisabled{false}; /// whether heart-beat is enabled bool enableHeartBeat{false}; }; class Protocol { public: /// current protocol version static const int protocol_version; // list of feature versions /// version from which receiver side progress reporting is supported static const int RECEIVER_PROGRESS_REPORT_VERSION; /// version from which checksum is supported static const int CHECKSUM_VERSION; /// version from which download resumption is supported static const int DOWNLOAD_RESUMPTION_VERSION; // list of encoding/decoding versions /// version from which flags are sent with settings cmd static const int SETTINGS_FLAG_VERSION; /// version from which flags and prevSeqId are sent with header cmd static const int HEADER_FLAG_AND_PREV_SEQ_ID_VERSION; /// version from which checkpoint started including file offset static const int CHECKPOINT_OFFSET_VERSION; /// version from which checkpoint started including seq-id static const int CHECKPOINT_SEQ_ID_VERSION; /// version from which wdt supports encryption static const int ENCRYPTION_V1_VERSION; /// version from which GCM tags were verified incrementally static const int INCREMENTAL_TAG_VERIFICATION_VERSION; /// version from which file deletion was supported for resumption static const int DELETE_CMD_VERSION; /// version from which we switched varint to better one static const int VARINT_CHANGE; /// version from which heart-beat was introduced static const int HEART_BEAT_VERSION; /// version from which wdt started to change encryption iv periodically static const int PERIODIC_ENCRYPTION_IV_CHANGE_VERSION; /// Both version, magic number and command byte enum CMD_MAGIC { DONE_CMD = 0x44, // D)one FILE_CMD = 0x4C, // L)oad WAIT_CMD = 0x57, // W)ait ERR_CMD = 0x45, // E)rr SETTINGS_CMD = 0x53, // S)ettings ABORT_CMD = 0x41, // A)bort CHUNKS_CMD = 0x43, // C)hunk ACK_CMD = 0x61, // a)ck SIZE_CMD = 0x5A, // Si(Z)e FOOTER_CMD = 0x46, // F)ooter LOCAL_CHECKPOINT_CMD = 0x01, // Local checkpoint cmd. This is a hack to ensure backward // compatibility. Since, the format of checkpoints is // <num_checkpoints><checkpoint1><checkpoint2>..., and since the // number of checkpoints for local checkpoint is 1, we can treat // 0x01 to be a separate cmd ENCRYPTION_CMD = 0x65, // (e)ncryption HEART_BEAT_CMD = 0x48, // (H)eart-beat }; // TODO: move the rest of those definitions closer to where they need to be // correct, ie in cpp like kAbortLength and kChunksCmdLen /// Max size of sender or receiver id static constexpr int64_t kMaxTransferIdLength = 1024; /// 1 byte for cmd, 2 bytes for file-name length, Max size of filename, 4 /// variants(seq-id, data-size, offset, file-size), 1 byte for flag, 10 bytes /// prev seq-id static constexpr int64_t kMaxHeader = 1 + 2 + PATH_MAX + 4 * 10 + 1 + 10; /// min number of bytes that must be send to unblock receiver static constexpr int64_t kMinBufLength = 256; /// max size of done command encoding(1 byte for cmd, 1 for status, 10 for /// number of blocks, 10 for number of bytes sent) static constexpr int64_t kMaxDone = 2 + 2 * 10; /// max length of the size cmd encoding static constexpr int64_t kMaxSize = 1 + 10; /// max size of settings command encoding static constexpr int64_t kMaxSettings = 1 + 3 * 10 + kMaxTransferIdLength + 1; /// max length of the footer cmd encoding, 10 byte for checksum static constexpr int64_t kMaxFooter = 1 + 10; /// max size of chunks cmd(4 bytes for buffer size and 4 bytes for number of /// files) static constexpr int64_t kChunksCmdLen = 2 * sizeof(int64_t); /// max size of chunkInfo encoding length static constexpr int64_t kMaxChunkEncodeLen = 20; /// abort cmd length(4 bytes for protocol, 1 byte for error-code and 8 bytes /// for checkpoint) static constexpr int64_t kAbortLength = sizeof(int32_t) + 1 + sizeof(int64_t); /// max size of version encoding static constexpr int64_t kMaxVersion = 10; /// max size of encryption cmd(1 byte for cmd, 1 byte for /// encryption type, rest for initialization vector and tag interval) static constexpr int64_t kEncryptionCmdLen = 1 + 1 + 1 + kAESBlockSize + sizeof(int32_t); static_assert(kMinBufLength <= kMaxHeader && kMaxSettings <= kMaxHeader, "Minimum buffer size is kMaxHeader. Header and Settings cmd " "must fit within the buffer"); /** * Return the library version, including protocol. * For debugging/identification purpose. */ static const std::string getFullVersion(); /** * Decides whether the current running wdt version can support the request * protocol version or not * * @param requestedProtocolVersion protocol version requested * @param curProtocolVersion current protocol version * * @return If current wdt supports the requested version or some lower * version, that version is returned. If it can not support the * requested version, 0 is returned */ static int negotiateProtocol(int requestedProtocolVersion, int curProtocolVersion = protocol_version); /// @return max local checkpoint length for a specific version static int getMaxLocalCheckpointLength(int protocolVersion); /// encodes blockDetails into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeHeader(int senderProtocolVersion, char *dest, int64_t &off, int64_t max, const BlockDetails &blockDetails); /// decodes from src+off and consumes/moves off but not past max /// sets BlockDetails /// @return false if there isn't enough data in src+off to src+max static bool decodeHeader(int receiverProtocolVersion, char *src, int64_t &off, int64_t max, BlockDetails &blockDetails); /// encodes checkpoints into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeCheckpoints(int protocolVersion, char *dest, int64_t &off, int64_t max, const std::vector<Checkpoint> &checkpoints); /// decodes from src+off and consumes/moves off but not past max /// sets checkpoints /// @return false if there isn't enough data in src+off to src+max static bool decodeCheckpoints(int protocolVersion, char *src, int64_t &off, int64_t max, std::vector<Checkpoint> &checkpoints); /// encodes numBlocks, totalBytes into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeDone(int protocolVersion, char *dest, int64_t &off, int64_t max, int64_t numBlocks, int64_t totalBytes); /// decodes from src+off and consumes/moves off but not past max /// sets numBlocks, totalBytes /// @return false if there isn't enough data in src+off to src+max static bool decodeDone(int protocolVersion, char *src, int64_t &off, int64_t max, int64_t &numBlocks, int64_t &totalBytes); /// encodes settings into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeSettings(int senderProtocolVersion, char *dest, int64_t &off, int64_t max, const Settings &settings); /// decodes from src+off and consumes/moves off but not past max /// sets senderProtocolVersion /// @return false if there isn't enough data in src+off to src+max static bool decodeVersion(char *src, int64_t &off, int64_t max, int &senderProtocolVersion); /// decodes from src+off and consumes/moves off but not past max /// sets settings /// @return false if there isn't enough data in src+off to src+max static bool decodeSettings(int protocolVersion, char *src, int64_t &off, int64_t max, Settings &settings); /// encodes encryption info into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeEncryptionSettings(char *dest, int64_t &off, int64_t max, const EncryptionType encryptionType, const std::string &iv, int32_t tagInterval); /// decodes from src+off and consumes/moves off but not past max /// sets encryption type, initializaion vector and tag interval /// @return false if there isn't enough data in src+off to src+max static bool decodeEncryptionSettings(char *src, int64_t &off, int64_t max, EncryptionType &encryptionType, std::string &iv, int32_t &tagInterval); /// encodes totalNumBytes into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeSize(char *dest, int64_t &off, int64_t max, int64_t totalNumBytes); /// decodes from src+off and consumes/moves off but not past max /// sets totalNumBytes /// @return false if there isn't enough data in src+off to src+max static bool decodeSize(char *src, int64_t &off, int64_t max, int64_t &totalNumBytes); /// encodes checksum or tag into dest+off /// moves the off into dest pointer, not going past max /// @return false if there isn't enough room to encode static bool encodeFooter(char *dest, int64_t &off, int64_t max, int32_t checksum); /// decodes from src+off and consumes/moves off but not past max /// sets checksum or tag /// @return false if there isn't enough data in src+off to src+max static bool decodeFooter(char *src, int64_t &off, int64_t max, int32_t &checksum); /// encodes protocolVersion, errCode and checkpoint into dest+off /// moves the off into dest pointer static bool encodeAbort(char *dest, int64_t &off, int64_t max, int32_t protocolVersion, ErrorCode errCode, int64_t checkpoint); /// decodes from src+off and consumes/moves off /// sets protocolversion, errcode, checkpoint static bool decodeAbort(char *src, int64_t &off, int64_t max, int32_t &protocolVersion, ErrorCode &errCode, int64_t &checkpoint); /// encodes bufSize and numFiles into dest+off /// moves the off into dest pointer static bool encodeChunksCmd(char *dest, int64_t &off, int64_t max, int64_t bufSize, int64_t numFiles); /// decodes from src+off and consumes/moves off /// sets bufSize and numFiles static bool decodeChunksCmd(char *src, int64_t &off, int64_t max, int64_t &bufSize, int64_t &numFiles); /// encodes chunk into dest+off /// moves the off into dest pointer static bool encodeChunkInfo(char *dest, int64_t &off, int64_t max, const Interval &chunk); /// decodes from src+off and consumes/moves off /// sets chunk /// @return false if there isn't enough data in src+off to src+max static bool decodeChunkInfo(folly::ByteRange &br, Interval &chunk); /// encodes fileChunksInfo into dest+off /// moves the off into dest pointer static bool encodeFileChunksInfo(char *dest, int64_t &off, int64_t max, const FileChunksInfo &fileChunksInfo); /// decodes from src+off and consumes/moves off /// sets fileChunksInfo /// @return false if there isn't enough data in src+off to src+max static bool decodeFileChunksInfo(folly::ByteRange &br, FileChunksInfo &fileChunksInfo); /** * returns maximum number of bytes to encode a given FileChunksInfo * * @param fileChunkInfo FileChunksInfo to encode * * @return max number of bytes to encode */ static int64_t maxEncodeLen(const FileChunksInfo &fileChunkInfo); /// encodes fileChunksInfo into dest+off /// moves the off into dest pointer /// returns number of fileChunks encoded static int64_t encodeFileChunksInfoList( char *dest, int64_t &off, int64_t bufSize, int64_t startIndex, const std::vector<FileChunksInfo> &fileChunksInfoList); /// decodes from src+off and consumes/moves off /// sets fileChunksInfoList /// @return false if there isn't enough data in src+off to src+max static bool decodeFileChunksInfoList( char *src, int64_t &off, int64_t dataSize, std::vector<FileChunksInfo> &fileChunksInfoList); }; } } // namespace facebook::wdt