From a38f40e3880b325e270d3d70108edc31912eb6e0 Mon Sep 17 00:00:00 2001 From: jyelon Date: Mon, 24 Jul 2023 17:22:35 -0400 Subject: [PATCH] Added base-writer class --- luprex/cpp/core/base-writer.hpp | 169 +++++++++++++++++++++++ luprex/cpp/core/streambuffer.cpp | 222 ++----------------------------- luprex/cpp/core/streambuffer.hpp | 73 +++------- 3 files changed, 202 insertions(+), 262 deletions(-) create mode 100644 luprex/cpp/core/base-writer.hpp diff --git a/luprex/cpp/core/base-writer.hpp b/luprex/cpp/core/base-writer.hpp new file mode 100644 index 00000000..8ba7fa55 --- /dev/null +++ b/luprex/cpp/core/base-writer.hpp @@ -0,0 +1,169 @@ +///////////////////////////////////////////////////////////////// +// +// IMPORTANT: This is a header-only library that is included +// by the graphics engine as well. It cannot contain references +// to anything else in the engine. +// + + +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////// +// +// BaseWriter +// +// This base class provides the following methods: +// +// void write_uint8(uint64_t data) +// void write_uint16(uint64_t data) +// void write_uint32(uint64_t data) +// void write_uint64(uint64_t data) +// void write_int8(int64_t data) +// void write_int16(int64_t data) +// void write_int32(int64_t data) +// void write_int64(int64_t data) +// void write_char(char c) +// void write_float(float data) +// void write_double(double data) +// void write_bytes(std::string_view bytes) +// void write_string(std::string_view bytes) +// +// In order to derive from BaseWriter, you must use the CRTP pattern: +// +// class DerivedWriter : public BaseWriter +// +// And you must provide two methods in the derived class: +// +// write_bytes(const char *n, size_t size) +// +// raise_truncated() +// +/////////////////////////////////////////////////////////////// + +template +class BaseWriter { +protected: + template + void write_value_core(T arg) { + static_cast(this)->write_bytes((const char *)&arg, sizeof(arg)); + } + + template + void write_int_core(XT arg) { + T reduced = arg; + if (XT(reduced) != arg) static_cast(this)->raise_truncated(); + write_value_core(reduced); + } + +public: + + void write_uint8(uint64_t data) { write_int_core(data); } + void write_uint16(uint64_t data) { write_int_core(data); } + void write_uint32(uint64_t data) { write_int_core(data); } + void write_uint64(uint64_t data) { write_int_core(data); } + void write_int8(int64_t data) { write_int_core(data); } + void write_int16(int64_t data) { write_int_core(data); } + void write_int32(int64_t data) { write_int_core(data); } + void write_int64(int64_t data) { write_int_core(data); } + + void write_bool(bool b) { write_uint8(b ? 1:0); } + void write_char(char c) { write_value_core(c); } + void write_float(float arg) { write_value_core(arg); } + void write_double(double arg) { write_value_core(arg); } + + void write_string(std::string_view s) { + if (s.size() >= 255) { + write_uint8(0xFF); + write_uint64(s.size()); + static_cast(this)->write_bytes(s.data(), s.size()); + } else { + write_uint8(s.size()); + static_cast(this)->write_bytes(s.data(), s.size()); + } + } +}; + +/////////////////////////////////////////////////////////////// +// +// BaseReader +// +// This base class provides the following methods: +// +// uint8_t read_uint8(); +// uint16_t read_uint16(); +// uint32_t read_uint32(); +// uint64_t read_uint64(); +// int8_t read_int8(); +// int16_t read_int16(); +// int32_t read_int32(); +// int64_t read_int64(); +// bool read_bool(); +// char read_char(); +// float read_float(); +// double read_double(); +// string read_string_limit(int64_t size); +// string read_string(); +// +// In order to derive from BaseReader, you must use the CRTP pattern: +// +// class DerivedReader : public BaseReader +// +// The derived class must provide: +// +// using string = std::string (or similar) +// +// void read_bytes_into(char *n, size_t size) +// +// void raise_string_too_long(); +// +/////////////////////////////////////////////////////////////// + +template +class BaseReader { +protected: + + template + T read_value_core() { + T result; + static_cast(this)->read_bytes_into((char *)(&result), sizeof(result)); + return result; + } + +public: + + uint8_t read_uint8() { return read_value_core(); } + uint16_t read_uint16() { return read_value_core(); } + uint32_t read_uint32() { return read_value_core(); } + uint64_t read_uint64() { return read_value_core(); } + int8_t read_int8() { return read_value_core(); } + int16_t read_int16() { return read_value_core(); } + int32_t read_int32() { return read_value_core(); } + int64_t read_int64() { return read_value_core(); } + + bool read_bool() { return read_uint8(); } + char read_char() { return read_value_core(); } + float read_float() { return read_value_core(); } + double read_double() { return read_value_core(); } + + String read_string_limit(int64_t limit) { + uint64_t size = read_uint8(); + if (size == 255) { + size = read_uint64(); + } + if ((limit < 0)||(size > uint64_t(limit))) { + static_cast(this)->raise_string_too_long(); + } + String result; + result.resize(size); + static_cast(this)->read_bytes_into(&result[0], size); + return result; + } + + String read_string() { + return read_string_limit(0xFFFFFFF); + } +}; + diff --git a/luprex/cpp/core/streambuffer.cpp b/luprex/cpp/core/streambuffer.cpp index e11dbdde..58449d07 100644 --- a/luprex/cpp/core/streambuffer.cpp +++ b/luprex/cpp/core/streambuffer.cpp @@ -145,41 +145,6 @@ eng::string StreamBuffer::readline() { } } -// These routines return true if you can losslessly cast the -// specified value to the specified type. - -static inline bool safe_to_cast_to_int8(int64_t vv) { - return ((vv + 0x80LL) & 0xFFFFFFFFFFFFFF00LL) == 0; -} - -static inline bool safe_to_cast_to_int16(int64_t vv) { - return ((vv + 0x8000LL) & 0xFFFFFFFFFFFF0000LL) == 0; -} - -static inline bool safe_to_cast_to_int32(int64_t vv) { - return ((vv + 0x80000000LL) & 0xFFFFFFFF00000000LL) == 0; -} - -static inline bool safe_to_cast_to_int64(int64_t vv) { - return true; -} - -static inline bool safe_to_cast_to_uint8(uint64_t vv) { - return (vv & 0xFFFFFFFFFFFFFF00LL) == 0; -} - -static inline bool safe_to_cast_to_uint16(uint64_t vv) { - return (vv & 0xFFFFFFFFFFFF0000LL) == 0; -} - -static inline bool safe_to_cast_to_uint32(uint64_t vv) { - return (vv & 0xFFFFFFFF00000000LL) == 0; -} - -static inline bool safe_to_cast_to_uint64(uint64_t vv) { - return true; -} - void StreamBuffer::write_bytes(const char *s, int64_t len) { make_space(len); memcpy(write_cursor_, s, len); @@ -187,11 +152,10 @@ void StreamBuffer::write_bytes(const char *s, int64_t len) { } void StreamBuffer::write_bytes(std::string_view s) { - make_space(s.size()); - memcpy(write_cursor_, s.data(), s.size()); - write_cursor_ += s.size(); + write_bytes(s.data(), s.size()); } + const char *StreamBuffer::read_bytes(int64_t bytes) { check_available(bytes); char *data = read_cursor_; @@ -199,87 +163,12 @@ const char *StreamBuffer::read_bytes(int64_t bytes) { return data; } -void StreamBuffer::write_int8(int64_t vv) { - assert(safe_to_cast_to_int8(vv)); - int8_t v = vv; - make_space(1); - memcpy(write_cursor_, &v, 1); - write_cursor_ += 1; +void StreamBuffer::read_bytes_into(char *data, int64_t size) { + check_available(size); + memcpy(data, read_cursor_, size); + read_cursor_ += size; } -void StreamBuffer::write_int16(int64_t vv) { - assert(safe_to_cast_to_int16(vv)); - int16_t v = vv; - make_space(2); - memcpy(write_cursor_, &v, 2); - write_cursor_ += 2; -} - -void StreamBuffer::write_int32(int64_t vv) { - assert(safe_to_cast_to_int32(vv)); - int32_t v = vv; - make_space(4); - memcpy(write_cursor_, &v, 4); - write_cursor_ += 4; -} - -void StreamBuffer::write_int64(int64_t vv) { - assert(safe_to_cast_to_int64(vv)); - int64_t v = vv; - make_space(8); - memcpy(write_cursor_, &v, 8); - write_cursor_ += 8; -} - -void StreamBuffer::write_uint8(uint64_t vv) { - assert(safe_to_cast_to_uint8(vv)); - uint8_t v = vv; - make_space(1); - memcpy(write_cursor_, &v, 1); - write_cursor_ += 1; -} - -void StreamBuffer::write_uint16(uint64_t vv) { - assert(safe_to_cast_to_uint16(vv)); - uint16_t v = vv; - make_space(2); - memcpy(write_cursor_, &v, 2); - write_cursor_ += 2; -} - -void StreamBuffer::write_uint32(uint64_t vv) { - assert(safe_to_cast_to_uint32(vv)); - uint32_t v = vv; - make_space(4); - memcpy(write_cursor_, &v, 4); - write_cursor_ += 4; -} - -void StreamBuffer::write_uint64(uint64_t vv) { - assert(safe_to_cast_to_uint64(vv)); - uint64_t v = vv; - make_space(8); - memcpy(write_cursor_, &v, 8); - write_cursor_ += 8; -} - -void StreamBuffer::write_char(char c) { - make_space(1); - write_cursor_[0] = c; - write_cursor_ += 1; -} - -void StreamBuffer::write_float(float f) { - make_space(4); - memcpy(write_cursor_, &f, 4); - write_cursor_ += 4; -} - -void StreamBuffer::write_double(double d) { - make_space(8); - memcpy(write_cursor_, &d, 8); - write_cursor_ += 8; -} void StreamBuffer::write_xyz(const util::XYZ &xyz) { make_space(12); @@ -301,61 +190,6 @@ void StreamBuffer::write_dxyz(const util::DXYZ &xyz) { write_cursor_ += 8; } -int8_t StreamBuffer::read_int8() { - check_available(1); - int8_t v; - memcpy(&v, read_cursor_, 1); - read_cursor_ += 1; - return v; -} - -int16_t StreamBuffer::read_int16() { - check_available(2); - int16_t v; - memcpy(&v, read_cursor_, 2); - read_cursor_ += 2; - return v; -} - -int32_t StreamBuffer::read_int32() { - check_available(4); - int32_t v; - memcpy(&v, read_cursor_, 4); - read_cursor_ += 4; - return v; -} - -int64_t StreamBuffer::read_int64() { - check_available(8); - int64_t v; - memcpy(&v, read_cursor_, 8); - read_cursor_ += 8; - return v; -} - -char StreamBuffer::read_char() { - check_available(1); - char c = read_cursor_[0]; - read_cursor_ += 1; - return c; -} - -float StreamBuffer::read_float() { - check_available(4); - float f; - memcpy(&f, read_cursor_, 4); - read_cursor_ += 4; - return f; -} - -double StreamBuffer::read_double() { - check_available(8); - double d; - memcpy(&d, read_cursor_, 8); - read_cursor_ += 8; - return d; -} - util::XYZ StreamBuffer::read_xyz() { check_available(12); util::XYZ result; @@ -380,45 +214,17 @@ util::DXYZ StreamBuffer::read_dxyz() { return result; } - - void StreamBuffer::write_hashvalue(const util::HashValue &hv) { write_uint64(hv.first); write_uint64(hv.second); } -void StreamBuffer::write_string(std::string_view s) { - if (s.size() >= 255) { - write_uint8(0xFF); - write_uint64(s.size()); - write_bytes(s); - } else { - write_uint8(s.size()); - write_bytes(s); - } -} - util::HashValue StreamBuffer::read_hashvalue() { uint64_t f = read_uint64(); uint64_t s = read_uint64(); return util::HashValue(f,s); } -eng::string StreamBuffer::read_string() { - return read_string_limit(0xFFFFFFF); -} - -eng::string StreamBuffer::read_string_limit(int64_t max_allowed) { - int64_t len = read_uint8(); - if (len == 255) { - len = read_int64(); - } - if (len < 0) throw StreamCorruption(); - if (len > max_allowed) throw StreamCorruption(); - const char *bytes = read_bytes(len); - return eng::string(bytes, len); -} - eng::string StreamBuffer::read_entire_contents() { eng::string result(read_cursor_, fill()); read_cursor_ = write_cursor_; @@ -426,57 +232,57 @@ eng::string StreamBuffer::read_entire_contents() { } void StreamBuffer::overwrite_int8(int64_t write_count_after, int64_t vv) { - assert(safe_to_cast_to_int8(vv)); int8_t v = vv; + assert(int64_t(v) == vv); char *target = get_overwrite(1, write_count_after); memcpy(target, &v, 1); } void StreamBuffer::overwrite_int16(int64_t write_count_after, int64_t vv) { - assert(safe_to_cast_to_int16(vv)); int16_t v = vv; + assert(int64_t(v) == vv); char *target = get_overwrite(2, write_count_after); memcpy(target, &v, 2); } void StreamBuffer::overwrite_int32(int64_t write_count_after, int64_t vv) { - assert(safe_to_cast_to_int32(vv)); int32_t v = vv; + assert(int64_t(v) == vv); char *target = get_overwrite(4, write_count_after); memcpy(target, &v, 4); } void StreamBuffer::overwrite_int64(int64_t write_count_after, int64_t vv) { - assert(safe_to_cast_to_int64(vv)); int64_t v = vv; + assert(int64_t(v) == vv); char *target = get_overwrite(8, write_count_after); memcpy(target, &v, 8); } void StreamBuffer::overwrite_uint8(int64_t write_count_after, uint64_t vv) { - assert(safe_to_cast_to_uint8(vv)); uint8_t v = vv; + assert(uint64_t(v) == vv); char *target = get_overwrite(1, write_count_after); memcpy(target, &v, 1); } void StreamBuffer::overwrite_uint16(int64_t write_count_after, uint64_t vv) { - assert(safe_to_cast_to_uint16(vv)); uint16_t v = vv; + assert(uint64_t(v) == vv); char *target = get_overwrite(2, write_count_after); memcpy(target, &v, 2); } void StreamBuffer::overwrite_uint32(int64_t write_count_after, uint64_t vv) { - assert(safe_to_cast_to_uint32(vv)); uint32_t v = vv; + assert(uint64_t(v) == vv); char *target = get_overwrite(4, write_count_after); memcpy(target, &v, 4); } void StreamBuffer::overwrite_uint64(int64_t write_count_after, uint64_t vv) { - assert(safe_to_cast_to_uint64(vv)); uint64_t v = vv; + assert(uint64_t(v) == vv); char *target = get_overwrite(8, write_count_after); memcpy(target, &v, 8); } diff --git a/luprex/cpp/core/streambuffer.hpp b/luprex/cpp/core/streambuffer.hpp index 2865ba5e..6db4ecb0 100644 --- a/luprex/cpp/core/streambuffer.hpp +++ b/luprex/cpp/core/streambuffer.hpp @@ -219,6 +219,7 @@ #include #include +#include "base-writer.hpp" #include "luastack.hpp" #include "util.hpp" @@ -240,8 +241,8 @@ public: virtual char const *what() const { return "Stream contained invalid data"; } }; -class StreamBuffer : public eng::nevernew { -public: +class StreamBuffer : public eng::nevernew, public BaseReader, public BaseWriter { +public: // Construct an empty buffer. StreamBuffer(); @@ -287,8 +288,12 @@ public: // It just writes the bytes. // void write_bytes(const char *bytes, int64_t len); - void write_bytes(std::string_view bytes); + void write_bytes(std::string_view s); + // Copy bytes from the StreamBuffer into an external buffer. + // + void read_bytes_into(char *target, int64_t len); + // Read a block of bytes from the buffer. // // Caution: the pointer returned is a pointer to the stream's buffer. It is @@ -297,63 +302,19 @@ public: // const char *read_bytes(int64_t bytes); - // Write integers and floats into the buffer. - // - // Note that integral parameters are all 64 bits. That's so that I can do - // runtime error checking to verify that the numbers are all in-range. - // - void write_int8(int64_t v); - void write_int16(int64_t v); - void write_int32(int64_t v); - void write_int64(int64_t v); - void write_uint8(uint64_t v); - void write_uint16(uint64_t v); - void write_uint32(uint64_t v); - void write_uint64(uint64_t v); - void write_char(char c); - void write_float(float f); - void write_double(double d); - void write_xyz(const util::XYZ &xyz); - void write_dxyz(const util::DXYZ &xyz); - - // Read fixed-size integers from the buffer. - // - // May throw StreamEof if the specified number of bytes aren't present. - // - int8_t read_int8(); - int16_t read_int16(); - int32_t read_int32(); - int64_t read_int64(); - uint8_t read_uint8() { return read_int8(); } - uint16_t read_uint16() { return read_int16(); } - uint32_t read_uint32() { return read_int32(); } - uint64_t read_uint64() { return read_int64(); } - char read_char(); - float read_float(); - double read_double(); - util::XYZ read_xyz(); - util::DXYZ read_dxyz(); - - // Write other types into the buffer. - // - // Note that strings are preceded by a length field. Reading - // a string works by reading the length field, and then reading - // the correct number of bytes. - // - void write_bool(bool b) { write_int8(b ? 1 : 0); } - void write_hashvalue(const util::HashValue &hv); - void write_string(std::string_view s); - - // Read other types from the buffer. + // Read and write larger types. // // Throws StreamEof if the specified number of bytes aren't present. // Read string with a length limit will throw 'StreamCorruption' if the // length is too long. // - bool read_bool() { return read_int8(); } + void write_xyz(const util::XYZ &xyz); + void write_dxyz(const util::DXYZ &xyz); + util::XYZ read_xyz(); + util::DXYZ read_dxyz(); + + void write_hashvalue(const util::HashValue &hv); util::HashValue read_hashvalue(); - eng::string read_string(); - eng::string read_string_limit(int64_t max_allowed); // Read the entire contents of the buffer as a string. // @@ -415,6 +376,10 @@ public: // Get an ostream that writes into the StreamBuffer. std::ostream &ostream(); + // Throw a StreamCorruption exception. + void raise_truncated() { throw StreamCorruption(); } + void raise_string_too_long() { throw StreamCorruption(); } + private: // Start and end of the allocated block. char *buf_lo_;