summaryrefslogtreecommitdiff
path: root/src/misc/endian_buffer.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/misc/endian_buffer.hpp')
-rw-r--r--src/misc/endian_buffer.hpp206
1 files changed, 206 insertions, 0 deletions
diff --git a/src/misc/endian_buffer.hpp b/src/misc/endian_buffer.hpp
new file mode 100644
index 000000000..c20d9a8b9
--- /dev/null
+++ b/src/misc/endian_buffer.hpp
@@ -0,0 +1,206 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/** @file endian_buffer.hpp Endian-aware buffer. */
+
+#ifndef ENDIAN_BUFFER_HPP
+#define ENDIAN_BUFFER_HPP
+
+#include <iterator>
+#include <string_view>
+#include "../core/span_type.hpp"
+#include "../core/bitmath_func.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 Tcont = typename std::vector<byte>, typename Titer = typename std::back_insert_iterator<Tcont>>
+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<byte>(data ? 1 : 0); }
+
+ template <typename... Targs>
+ EndianBufferWriter &operator <<(const std::tuple<Targs...> &data)
+ {
+ this->WriteTuple(data, std::index_sequence_for<Targs...>{});
+ return *this;
+ }
+
+ template <class T, std::enable_if_t<std::disjunction_v<std::negation<std::is_class<T>>, std::is_base_of<StrongTypedefBase, T>>, int> = 0>
+ EndianBufferWriter &operator <<(const T data)
+ {
+ if constexpr (std::is_enum_v<T>) {
+ this->Write(static_cast<std::underlying_type_t<const T>>(data));
+ } else if constexpr (std::is_base_of_v<StrongTypedefBase, T>) {
+ this->Write(data.value);
+ } else {
+ this->Write(data);
+ }
+ return *this;
+ }
+
+ template <typename Tvalue, typename Tbuf = std::vector<byte>>
+ 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<class Ttuple, size_t... Tindices>
+ void WriteTuple(const Ttuple &values, std::index_sequence<Tindices...>) {
+ ((*this << std::get<Tindices>(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 <class T>
+ 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. */
+ span<const byte> buffer;
+ /** Current read position. */
+ size_t read_pos = 0;
+
+public:
+ EndianBufferReader(span<const byte> 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<byte>() != 0; return *this; }
+
+ template <typename... Targs>
+ EndianBufferReader &operator >>(std::tuple<Targs...> &data)
+ {
+ this->ReadTuple(data, std::index_sequence_for<Targs...>{});
+ return *this;
+ }
+
+ template <class T, std::enable_if_t<std::disjunction_v<std::negation<std::is_class<T>>, std::is_base_of<StrongTypedefBase, T>>, int> = 0>
+ EndianBufferReader &operator >>(T &data)
+ {
+ if constexpr (std::is_enum_v<T>) {
+ data = static_cast<T>(this->Read<std::underlying_type_t<T>>());
+ } else if constexpr (std::is_base_of_v<StrongTypedefBase, T>) {
+ data.value = this->Read<decltype(data.value)>();
+ } else {
+ data = this->Read<T>();
+ }
+ return *this;
+ }
+
+ template <typename Tvalue>
+ static Tvalue ToValue(span<const byte> buffer)
+ {
+ Tvalue result{};
+ EndianBufferReader reader{ buffer };
+ reader >> result;
+ return result;
+ }
+
+private:
+ /** Helper function to read a tuple from the buffer. */
+ template<class Ttuple, size_t... Tindices>
+ void ReadTuple(Ttuple &values, std::index_sequence<Tindices...>) {
+ ((*this >> std::get<Tindices>(values)), ...);
+ }
+
+ /** Read overload for string data. */
+ std::string ReadStr()
+ {
+ std::string str;
+ while (this->read_pos < this->buffer.size()) {
+ char ch = this->Read<char>();
+ if (ch == '\0') break;
+ str.push_back(ch);
+ }
+ return str;
+ }
+
+ /** Fundamental read function. */
+ template <class T>
+ T Read()
+ {
+ static_assert(!std::is_const_v<T>, "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<T>(this->buffer[this->read_pos++]);
+ if constexpr (sizeof(T) > 1) {
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 8;
+ }
+ if constexpr (sizeof(T) > 2) {
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 16;
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 24;
+ }
+ if constexpr (sizeof(T) > 4) {
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 32;
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 40;
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 48;
+ value += static_cast<T>(this->buffer[this->read_pos++]) << 56;
+ }
+
+ return value;
+ }
+};
+
+#endif /* ENDIAN_BUFFER_HPP */