// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** #ifndef SERIALIZE_H_839405783574356 #define SERIALIZE_H_839405783574356 #include #include #include #include "string_base.h" //keep header clean from specific stream implementations! (e.g.file_io.h)! used by abstract.h! namespace zen { //high-performance unformatted serialization (avoiding wxMemoryOutputStream/wxMemoryInputStream inefficiencies) /* -------------------------- |Binary Container Concept| -------------------------- binary container for data storage: must support "basic" std::vector interface (e.g. std::vector, std::string, Zbase) */ //binary container reference implementations using Utf8String = Zbase; //ref-counted + COW text stream + guaranteed performance: exponential growth class ByteArray; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) class ByteArray //essentially a std::vector with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite { public: using value_type = std::vector::value_type; using iterator = std::vector::iterator; using const_iterator = std::vector::const_iterator; iterator begin() { return buffer_.ref().begin(); } iterator end () { return buffer_.ref().end (); } const_iterator begin() const { return buffer_.ref().begin(); } const_iterator end () const { return buffer_.ref().end (); } void resize(size_t len) { buffer_.ref().resize(len); } size_t size() const { return buffer_.ref().size(); } bool empty() const { return buffer_.ref().empty(); } inline friend bool operator==(const ByteArray& lhs, const ByteArray& rhs) { return lhs.buffer_.ref() == rhs.buffer_.ref(); } private: SharedRef> buffer_ = makeSharedRef>(); //perf: shared_ptr indirection irrelevant: less than 1% slower! }; /* ------------------------------- |Buffered Input Stream Concept| ------------------------------- struct BufferedInputStream { size_t read(void* buffer, size_t bytesToRead); //throw X; return "bytesToRead" bytes unless end of stream! Optional: support stream-copying -------------------------------- size_t getBlockSize() const; const IOCallback& notifyUnbufferedIO }; -------------------------------- |Buffered Output Stream Concept| -------------------------------- struct BufferedOutputStream { void write(const void* buffer, size_t bytesToWrite); //throw X Optional: support stream-copying -------------------------------- const IOCallback& notifyUnbufferedIO }; */ using IOCallback = std::function; //throw X //functions based on buffered stream abstraction template void bufferedStreamCopy(BufferedInputStream& streamIn, BufferedOutputStream& streamOut); //throw X template BinContainer bufferedLoad(BufferedInputStream& streamIn); //throw X template void writeNumber (BufferedOutputStream& stream, const N& num); // template void writeContainer(BufferedOutputStream& stream, const C& str); //noexcept template < class BufferedOutputStream> void writeArray (BufferedOutputStream& stream, const void* buffer, size_t len); // //---------------------------------------------------------------------- class UnexpectedEndOfStreamError {}; template N readNumber (BufferedInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data) template C readContainer(BufferedInputStream& stream); // template < class BufferedInputStream> void readArray (BufferedInputStream& stream, void* buffer, size_t len); // struct IOCallbackDivider { IOCallbackDivider(const IOCallback& notifyUnbufferedIO, int64_t& totalUnbufferedIO) : totalUnbufferedIO_(totalUnbufferedIO), notifyUnbufferedIO_(notifyUnbufferedIO) {} void operator()(int64_t bytesDelta) { if (notifyUnbufferedIO_) notifyUnbufferedIO_((totalUnbufferedIO_ - totalUnbufferedIO_ / 2 * 2 + bytesDelta) / 2); //throw X! totalUnbufferedIO_ += bytesDelta; } private: int64_t& totalUnbufferedIO_; const IOCallback& notifyUnbufferedIO_; }; //buffered input/output stream reference implementations: template struct MemoryStreamIn { MemoryStreamIn(const BinContainer& cont) : buffer_(cont) {} //this better be cheap! size_t read(void* buffer, size_t bytesToRead) //return "bytesToRead" bytes unless end of stream! { using Byte = typename BinContainer::value_type; static_assert(sizeof(Byte) == 1); const size_t bytesRead = std::min(bytesToRead, buffer_.size() - pos_); auto itFirst = buffer_.begin() + pos_; std::copy(itFirst, itFirst + bytesRead, static_cast(buffer)); pos_ += bytesRead; return bytesRead; } size_t pos() const { return pos_; } private: MemoryStreamIn (const MemoryStreamIn&) = delete; MemoryStreamIn& operator=(const MemoryStreamIn&) = delete; const BinContainer buffer_; size_t pos_ = 0; }; template struct MemoryStreamOut { MemoryStreamOut() = default; void write(const void* buffer, size_t bytesToWrite) { using Byte = typename BinContainer::value_type; static_assert(sizeof(Byte) == 1); buffer_.resize(buffer_.size() + bytesToWrite); const auto it = static_cast(buffer); std::copy(it, it + bytesToWrite, buffer_.end() - bytesToWrite); } const BinContainer& ref() const { return buffer_; } private: MemoryStreamOut (const MemoryStreamOut&) = delete; MemoryStreamOut& operator=(const MemoryStreamOut&) = delete; BinContainer buffer_; }; //-----------------------implementation------------------------------- template inline void bufferedStreamCopy(BufferedInputStream& streamIn, //throw X BufferedOutputStream& streamOut) // { const size_t blockSize = streamIn.getBlockSize(); if (blockSize == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); std::vector buffer(blockSize); for (;;) { const size_t bytesRead = streamIn.read(&buffer[0], blockSize); //throw X; return "bytesToRead" bytes unless end of stream! streamOut.write(&buffer[0], bytesRead); //throw X if (bytesRead < blockSize) //end of file break; } } template inline BinContainer bufferedLoad(BufferedInputStream& streamIn) //throw X { static_assert(sizeof(typename BinContainer::value_type) == 1); //expect: bytes const size_t blockSize = streamIn.getBlockSize(); if (blockSize == 0) throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo(__LINE__)); BinContainer buffer; for (;;) { buffer.resize(buffer.size() + blockSize); const size_t bytesRead = streamIn.read(&*(buffer.end() - blockSize), blockSize); //throw X; return "bytesToRead" bytes unless end of stream! buffer.resize(buffer.size() - blockSize + bytesRead); //caveat: unsigned arithmetics if (bytesRead < blockSize) //end of file return buffer; } } template inline void writeArray(BufferedOutputStream& stream, const void* buffer, size_t len) { stream.write(buffer, len); } template inline void writeNumber(BufferedOutputStream& stream, const N& num) { static_assert(IsArithmetic::value || std::is_same_v); writeArray(stream, &num, sizeof(N)); } template inline void writeContainer(BufferedOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here, we're handling arbitrary binary data! { const auto len = cont.size(); writeNumber(stream, static_cast(len)); if (len > 0) writeArray(stream, &*cont.begin(), sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface } template inline void readArray(BufferedInputStream& stream, void* buffer, size_t len) //throw UnexpectedEndOfStreamError { const size_t bytesRead = stream.read(buffer, len); assert(bytesRead <= len); //buffer overflow otherwise not always detected! if (bytesRead < len) throw UnexpectedEndOfStreamError(); } template inline N readNumber(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError { static_assert(IsArithmetic::value || std::is_same_v); N num = 0; readArray(stream, &num, sizeof(N)); //throw UnexpectedEndOfStreamError return num; } template inline C readContainer(BufferedInputStream& stream) //throw UnexpectedEndOfStreamError { C cont; auto strLength = readNumber(stream); //throw UnexpectedEndOfStreamError if (strLength > 0) { try { cont.resize(strLength); //throw std::bad_alloc } catch (std::bad_alloc&) //most likely this is due to data corruption! { throw UnexpectedEndOfStreamError(); } readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); //throw UnexpectedEndOfStreamError } return cont; } } #endif //SERIALIZE_H_839405783574356