/*
* 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 */