/* * This file is part of OpenTTD. * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . */ /** @file endian_buffer.hpp Endian-aware buffer. */ #ifndef ENDIAN_BUFFER_HPP #define ENDIAN_BUFFER_HPP #include #include "../core/bitmath_func.hpp" #include "../core/overflowsafe_type.hpp" struct StrongTypedefBase; /** * Endian-aware buffer adapter that always writes values in little endian order. * @note This class uses operator overloading (<<, just like streams) for writing * as this allows providing custom operator overloads for more complex types * like e.g. structs without needing to modify this class. */ template , typename Titer = typename std::back_insert_iterator> class EndianBufferWriter { /** Output iterator for the destination buffer. */ Titer buffer; public: EndianBufferWriter(Titer buffer) : buffer(buffer) {} EndianBufferWriter(typename Titer::container_type &container) : buffer(std::back_inserter(container)) {} EndianBufferWriter &operator <<(const std::string &data) { return *this << std::string_view{ data }; } EndianBufferWriter &operator <<(const char *data) { return *this << std::string_view{ data }; } EndianBufferWriter &operator <<(std::string_view data) { this->Write(data); return *this; } EndianBufferWriter &operator <<(bool data) { return *this << static_cast(data ? 1 : 0); } template EndianBufferWriter &operator <<(const OverflowSafeInt &data) { return *this << static_cast(data); }; template EndianBufferWriter &operator <<(const std::tuple &data) { this->WriteTuple(data, std::index_sequence_for{}); return *this; } template >, std::is_base_of>, int> = 0> EndianBufferWriter &operator <<(const T data) { if constexpr (std::is_enum_v) { this->Write(static_cast>(data)); } else if constexpr (std::is_base_of_v) { this->Write(data.base()); } else { this->Write(data); } return *this; } template > static Tbuf FromValue(const Tvalue &data) { Tbuf buffer; EndianBufferWriter writer{ buffer }; writer << data; return buffer; } private: /** Helper function to write a tuple to the buffer. */ template void WriteTuple(const Ttuple &values, std::index_sequence) { ((*this << std::get(values)), ...); } /** Write overload for string values. */ void Write(std::string_view value) { for (auto c : value) { this->buffer++ = c; } this->buffer++ = '\0'; } /** Fundamental write function. */ template void Write(T value) { static_assert(sizeof(T) <= 8, "Value can't be larger than 8 bytes"); if constexpr (sizeof(T) > 1) { this->buffer++ = GB(value, 0, 8); this->buffer++ = GB(value, 8, 8); if constexpr (sizeof(T) > 2) { this->buffer++ = GB(value, 16, 8); this->buffer++ = GB(value, 24, 8); } if constexpr (sizeof(T) > 4) { this->buffer++ = GB(value, 32, 8); this->buffer++ = GB(value, 40, 8); this->buffer++ = GB(value, 48, 8); this->buffer++ = GB(value, 56, 8); } } else { this->buffer++ = value; } } }; /** * Endian-aware buffer adapter that always reads values in little endian order. * @note This class uses operator overloading (>>, just like streams) for reading * as this allows providing custom operator overloads for more complex types * like e.g. structs without needing to modify this class. */ class EndianBufferReader { /** Reference to storage buffer. */ std::span buffer; /** Current read position. */ size_t read_pos = 0; public: EndianBufferReader(std::span buffer) : buffer(buffer) {} void rewind() { this->read_pos = 0; } EndianBufferReader &operator >>(std::string &data) { data = this->ReadStr(); return *this; } EndianBufferReader &operator >>(bool &data) { data = this->Read() != 0; return *this; } template EndianBufferReader &operator >>(OverflowSafeInt &data) { data = this->Read(); return *this; }; template EndianBufferReader &operator >>(std::tuple &data) { this->ReadTuple(data, std::index_sequence_for{}); return *this; } template >, std::is_base_of>, int> = 0> EndianBufferReader &operator >>(T &data) { if constexpr (std::is_enum_v) { data = static_cast(this->Read>()); } else if constexpr (std::is_base_of_v) { data = this->Read(); } else { data = this->Read(); } return *this; } template static Tvalue ToValue(std::span buffer) { Tvalue result{}; EndianBufferReader reader{ buffer }; reader >> result; return result; } private: /** Helper function to read a tuple from the buffer. */ template void ReadTuple(Ttuple &values, std::index_sequence) { ((*this >> std::get(values)), ...); } /** Read overload for string data. */ std::string ReadStr() { std::string str; while (this->read_pos < this->buffer.size()) { char ch = this->Read(); if (ch == '\0') break; str.push_back(ch); } return str; } /** Fundamental read function. */ template T Read() { static_assert(!std::is_const_v, "Can't read into const variables"); static_assert(sizeof(T) <= 8, "Value can't be larger than 8 bytes"); if (read_pos + sizeof(T) > this->buffer.size()) return {}; T value = static_cast(this->buffer[this->read_pos++]); if constexpr (sizeof(T) > 1) { value += static_cast(this->buffer[this->read_pos++]) << 8; } if constexpr (sizeof(T) > 2) { value += static_cast(this->buffer[this->read_pos++]) << 16; value += static_cast(this->buffer[this->read_pos++]) << 24; } if constexpr (sizeof(T) > 4) { value += static_cast(this->buffer[this->read_pos++]) << 32; value += static_cast(this->buffer[this->read_pos++]) << 40; value += static_cast(this->buffer[this->read_pos++]) << 48; value += static_cast(this->buffer[this->read_pos++]) << 56; } return value; } }; #endif /* ENDIAN_BUFFER_HPP */