changeset 364:99c961c91809

core: refactor out byte stream into its own file easy dubs
author Paper <paper@paper.us.eu.org>
date Tue, 16 Jul 2024 21:15:59 -0400
parents f10507d8f686
children f81bed4e04ac
files CMakeLists.txt dep/pugixml/pugixml-config-version.cmake dep/pugixml/pugixml-config.cmake dep/pugixml/pugixml.pc include/core/bit_cast.h include/core/byte_stream.h include/core/endian.h include/sys/x11/settings.h src/core/byte_stream.cc src/sys/glib/dark_theme.cc src/sys/x11/dark_theme.cc src/sys/x11/settings.cc
diffstat 12 files changed, 337 insertions(+), 117 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Mon Jul 15 01:33:51 2024 -0400
+++ b/CMakeLists.txt	Tue Jul 16 21:15:59 2024 -0400
@@ -85,6 +85,7 @@
 	src/core/anime.cc
 	src/core/anime_db.cc
 	src/core/anime_season.cc
+	src/core/byte_stream.cc
 	src/core/config.cc
 	src/core/date.cc
 	src/core/filesystem.cc
@@ -204,6 +205,15 @@
 ###########################################################################
 # Platform specific stuff
 
+# Endianness
+include (TestBigEndian)
+TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
+if(IS_BIG_ENDIAN)
+	list(APPEND DEFINES BYTE_ORDER_BIG)
+else()
+	list(APPEND DEFINES BYTE_ORDER_LITTLE)
+endif()
+
 # This is also used in the Win32 rc file
 set(RC_INFO_STRING "A lightweight anime tracker built with Qt.")
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/pugixml/pugixml-config-version.cmake	Tue Jul 16 21:15:59 2024 -0400
@@ -0,0 +1,65 @@
+# This is a basic version file for the Config-mode of find_package().
+# It is used by write_basic_package_version_file() as input file for configure_file()
+# to create a version-file which can be installed along a config.cmake file.
+#
+# The created file sets PACKAGE_VERSION_EXACT if the current version string and
+# the requested version string are exactly the same and it sets
+# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version,
+# but only if the requested major version is the same as the current one.
+# The variable CVF_VERSION must be set before calling configure_file().
+
+
+set(PACKAGE_VERSION "1.14")
+
+if(PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION)
+  set(PACKAGE_VERSION_COMPATIBLE FALSE)
+else()
+
+  if("1.14" MATCHES "^([0-9]+)\\.")
+    set(CVF_VERSION_MAJOR "${CMAKE_MATCH_1}")
+    if(NOT CVF_VERSION_MAJOR VERSION_EQUAL 0)
+      string(REGEX REPLACE "^0+" "" CVF_VERSION_MAJOR "${CVF_VERSION_MAJOR}")
+    endif()
+  else()
+    set(CVF_VERSION_MAJOR "1.14")
+  endif()
+
+  if(PACKAGE_FIND_VERSION_RANGE)
+    # both endpoints of the range must have the expected major version
+    math (EXPR CVF_VERSION_MAJOR_NEXT "${CVF_VERSION_MAJOR} + 1")
+    if (NOT PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
+        OR ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX_MAJOR STREQUAL CVF_VERSION_MAJOR)
+          OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND NOT PACKAGE_FIND_VERSION_MAX VERSION_LESS_EQUAL CVF_VERSION_MAJOR_NEXT)))
+      set(PACKAGE_VERSION_COMPATIBLE FALSE)
+    elseif(PACKAGE_FIND_VERSION_MIN_MAJOR STREQUAL CVF_VERSION_MAJOR
+        AND ((PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "INCLUDE" AND PACKAGE_VERSION VERSION_LESS_EQUAL PACKAGE_FIND_VERSION_MAX)
+        OR (PACKAGE_FIND_VERSION_RANGE_MAX STREQUAL "EXCLUDE" AND PACKAGE_VERSION VERSION_LESS PACKAGE_FIND_VERSION_MAX)))
+      set(PACKAGE_VERSION_COMPATIBLE TRUE)
+    else()
+      set(PACKAGE_VERSION_COMPATIBLE FALSE)
+    endif()
+  else()
+    if(PACKAGE_FIND_VERSION_MAJOR STREQUAL CVF_VERSION_MAJOR)
+      set(PACKAGE_VERSION_COMPATIBLE TRUE)
+    else()
+      set(PACKAGE_VERSION_COMPATIBLE FALSE)
+    endif()
+
+    if(PACKAGE_FIND_VERSION STREQUAL PACKAGE_VERSION)
+      set(PACKAGE_VERSION_EXACT TRUE)
+    endif()
+  endif()
+endif()
+
+
+# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
+if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "8" STREQUAL "")
+  return()
+endif()
+
+# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
+if(NOT CMAKE_SIZEOF_VOID_P STREQUAL "8")
+  math(EXPR installedBits "8 * 8")
+  set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
+  set(PACKAGE_VERSION_UNSUITABLE TRUE)
+endif()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/pugixml/pugixml-config.cmake	Tue Jul 16 21:15:59 2024 -0400
@@ -0,0 +1,19 @@
+
+####### Expanded from @PACKAGE_INIT@ by configure_package_config_file() #######
+####### Any changes to this file will be overwritten by the next CMake run ####
+####### The input file was pugixml-config.cmake.in                            ########
+
+get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}/../" ABSOLUTE)
+
+####################################################################################
+
+include("${CMAKE_CURRENT_LIST_DIR}/pugixml-targets.cmake")
+
+# If the user is not requiring 1.11 (either by explicitly requesting an older
+# version or not requesting one at all), provide the old imported target name
+# for compatibility.
+if (NOT TARGET pugixml AND (NOT DEFINED PACKAGE_FIND_VERSION OR PACKAGE_FIND_VERSION VERSION_LESS "1.11"))
+  add_library(pugixml INTERFACE IMPORTED)
+  # Equivalent to target_link_libraries INTERFACE, but compatible with CMake 3.10
+  set_target_properties(pugixml PROPERTIES INTERFACE_LINK_LIBRARIES pugixml::pugixml)
+endif ()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/dep/pugixml/pugixml.pc	Tue Jul 16 21:15:59 2024 -0400
@@ -0,0 +1,11 @@
+prefix=/usr
+exec_prefix=${prefix}
+includedir=/usr/include
+libdir=/usr/lib
+
+Name: pugixml
+Description: Light-weight, simple and fast XML parser for C++ with XPath support.
+URL: https://pugixml.org/
+Version: 1.14
+Cflags: -I${includedir}
+Libs: -L${libdir} -lpugixml
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/core/bit_cast.h	Tue Jul 16 21:15:59 2024 -0400
@@ -0,0 +1,26 @@
+#ifndef MINORI_CORE_BIT_CAST_H_
+#define MINORI_CORE_BIT_CAST_H_
+
+/* XXX need to move more "core" stuff into the minori namespace */
+
+#include <type_traits>
+#include <memory>
+#include <cstring>
+
+namespace minori {
+
+/* C++17 doesn't have this unfortunately */
+template<typename To, class From>
+To BitCast(From from) {
+	static_assert(sizeof(From) == sizeof(To), "Types must match sizes");
+	static_assert(std::is_pod<From>::value, "Requires POD input");
+	static_assert(std::is_pod<To>::value, "Requires POD output");
+
+	To to;
+	std::memcpy(std::addressof(to), std::addressof(from), sizeof(from));
+	return to;
+}
+
+}
+
+#endif /* MINORI_CORE_BIT_CAST_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/include/core/byte_stream.h	Tue Jul 16 21:15:59 2024 -0400
@@ -0,0 +1,75 @@
+#ifndef MINORI_CORE_BYTE_STREAM_H_
+#define MINORI_CORE_BYTE_STREAM_H_
+
+#include "core/endian.h"
+
+#include <string>
+#include <vector>
+#include <cstdint>
+#include <type_traits>
+
+struct ByteStream {
+public:
+	enum class ByteOrder {
+		Little,
+		Big,
+	};
+
+	ByteStream(std::uint8_t *bytes, std::size_t size);
+
+	void ResetOffset();
+	void SetEndianness(ByteOrder endian);
+
+	template<typename T>
+	bool ReadBinary(T& ret) {
+		if (offset_ + sizeof(T) >= size_)
+			return false;
+
+		ret = *reinterpret_cast<T*>(bytes_ + offset_);
+		Advance(sizeof(T));
+		return true;
+	}
+
+	template<typename T>
+	bool ReadInt(T& ret) {
+		static_assert(std::is_integral<T>::value);
+
+		if (!ReadBinary<T>(ret))
+			return false;
+
+		switch (endian_) {
+			case ByteOrder::Little:
+				if constexpr (std::is_unsigned<T>::value) {
+					ret = Endian::byteswap_little_to_host(ret);
+				} else {
+					ret = Endian::signed_byteswap_little_to_host(ret);
+				}
+				break;
+			case ByteOrder::Big:
+				if constexpr (std::is_unsigned<T>::value) {
+					ret = Endian::byteswap_big_to_host(ret);
+				} else {
+					ret = Endian::signed_byteswap_big_to_host(ret);
+				}
+				break;
+			default:
+				/* can't know for sure. punt */
+				return false;
+		}
+
+		return true;
+	}
+
+	bool ReadString(std::string& str, std::size_t size);
+	bool Advance(std::size_t amount);
+
+private:
+	/* raw data */
+	std::uint8_t *bytes_ = nullptr;
+	std::size_t offset_ = 0;
+	std::size_t size_ = 0;
+
+	ByteOrder endian_ = ByteOrder::Little;
+};
+
+#endif /* MINORI_CORE_BYTE_STREAM_H_ */
--- a/include/core/endian.h	Mon Jul 15 01:33:51 2024 -0400
+++ b/include/core/endian.h	Tue Jul 16 21:15:59 2024 -0400
@@ -1,16 +1,15 @@
 #ifndef MINORI_CORE_ENDIAN_H_
 #define MINORI_CORE_ENDIAN_H_
 
-/* definition of endian-related stuff. primarily used for*/
+/* definition of endian-related stuff. primarily used for x11
+ * binary structures */
 
+#include "core/bit_cast.h"
 #include <cstdint>
 #include <type_traits>
 
 class Endian {
 private:
-	static constexpr uint32_t uint32_ = 0x01020304;
-	static constexpr uint8_t magic_ = static_cast<const uint8_t&>(uint32_);
-
 	/* check for compiler builtins for byteswapping */
 #ifdef __has_builtin
 #	if __has_builtin(__builtin_bswap16)
@@ -75,14 +74,20 @@
 #	undef COMPILER_BUILTIN_BSWAP64
 #endif
 public:
-	static constexpr bool little = magic_ == 0x04;
-	static constexpr bool big = magic_ == 0x01;
-	static_assert(little || big, "unsupported endianness");
+#if defined(BYTE_ORDER_BIG)
+	static constexpr bool big = true;
+	static constexpr bool little = false;
+#elif defined(BYTE_ORDER_LITTLE)
+	static constexpr bool big = false;
+	static constexpr bool little = true;
+#else
+#error "unsupported endianness"
+#endif
 
 	template<typename T>
 	static constexpr T byteswap(T x) {
 		static_assert(std::is_integral<T>::value);
-		static_assert(std::is_unsigned<T>::value);
+		static_assert(std::is_unsigned<T>::value, "use signed_byteswap");
 
 		if constexpr (std::is_same<T, uint8_t>::value) {
 			return x;
@@ -97,6 +102,16 @@
 		}
 	}
 
+	/* this can't be constexpr */
+	template<typename T>
+	static T signed_byteswap(T x) {
+		static_assert(std::is_integral<T>::value);
+		static_assert(std::is_signed<T>::value, "use regular byteswap");
+
+		using uT = typename std::make_unsigned<T>::type;
+		return minori::BitCast<T, uT>(byteswap<uT>(minori::BitCast<uT, T>(x)));
+	}
+
 	template<typename T>
 	static constexpr T byteswap_little_to_host(T x) {
 		if constexpr (little) {
@@ -114,6 +129,24 @@
 			return byteswap(x);
 		}
 	}
+
+	template<typename T>
+	static T signed_byteswap_little_to_host(T x) {
+		if constexpr (little) {
+			return x;
+		} else if constexpr (big) {
+			return signed_byteswap(x);
+		}
+	}
+
+	template<typename T>
+	static T signed_byteswap_big_to_host(T x) {
+		if constexpr (big) {
+			return x;
+		} else if constexpr (little) {
+			return signed_byteswap(x);
+		}
+	}
 private:
 	Endian() = delete;
 };
--- a/include/sys/x11/settings.h	Mon Jul 15 01:33:51 2024 -0400
+++ b/include/sys/x11/settings.h	Tue Jul 16 21:15:59 2024 -0400
@@ -17,7 +17,7 @@
 
 	/* could technically be a union */
 	struct Data {
-		std::uint32_t integer;
+		std::int32_t integer;
 		std::string string;
 		struct {
 			std::uint16_t red, green, blue, alpha;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/core/byte_stream.cc	Tue Jul 16 21:15:59 2024 -0400
@@ -0,0 +1,31 @@
+#include "core/byte_stream.h"
+
+ByteStream::ByteStream(std::uint8_t *bytes, std::size_t size) {
+	bytes_ = bytes;
+	size_ = size;
+}
+
+void ByteStream::ResetOffset() {
+	offset_ = 0;
+}
+
+void ByteStream::SetEndianness(ByteStream::ByteOrder endian) {
+	endian_ = endian;
+}
+
+bool ByteStream::ReadString(std::string& str, std::size_t size) {
+	if (offset_ + size >= size_)
+		return false;
+
+	str.assign(reinterpret_cast<const char *>(bytes_ + offset_), size);
+	Advance(size);
+	return true;
+}
+
+bool ByteStream::Advance(std::size_t amount) {
+	if (offset_ + amount >= size_)
+		return false;
+
+	offset_ += amount;
+	return true;
+}
--- a/src/sys/glib/dark_theme.cc	Mon Jul 15 01:33:51 2024 -0400
+++ b/src/sys/glib/dark_theme.cc	Tue Jul 16 21:15:59 2024 -0400
@@ -5,7 +5,6 @@
 #include <string_view>
 #include <memory>
 #include <array>
-#include <iostream>
 
 namespace glib {
 
--- a/src/sys/x11/dark_theme.cc	Mon Jul 15 01:33:51 2024 -0400
+++ b/src/sys/x11/dark_theme.cc	Tue Jul 16 21:15:59 2024 -0400
@@ -2,8 +2,6 @@
 #include "sys/x11/settings.h"
 #include "sys/glib/dark_theme.h" /* glib::IsGTKThemeDark */
 
-#include <iostream>
-
 namespace x11 {
 
 bool IsInDarkTheme() {
--- a/src/sys/x11/settings.cc	Mon Jul 15 01:33:51 2024 -0400
+++ b/src/sys/x11/settings.cc	Tue Jul 16 21:15:59 2024 -0400
@@ -1,6 +1,7 @@
 #include "sys/x11/settings.h"
-#include "core/endian.h"
+#include "core/byte_stream.h"
 
+#include <cassert>
 #include <cstring>
 #include <cstdint>
 #include <climits>
@@ -8,7 +9,6 @@
 #include <memory>
 #include <array>
 #include <optional>
-#include <iostream>
 #include <map>
 
 #include <xcb/xcb.h>
@@ -40,61 +40,20 @@
 public:
 	Parser(std::uint8_t *bytes, std::size_t size);
 
-	std::vector<SettingsItem> ParseAllItems(void);
+	bool ParseHeader(void);
 	std::optional<SettingsItem> ParseNextItem(void);
 
 	std::uint32_t GetTotalItems(void);
 
 private:
-	/* byte order values */
-	enum {
+	enum ByteOrder {
 		LSBFirst = 0,
 		MSBFirst = 1,
 	};
 
-	template<typename T>
-	bool ReadData(T& ret) {
-		if (offset_ + sizeof(T) >= size_)
-			return false;
-
-		ret = *reinterpret_cast<T*>(bytes_ + offset_);
-		Advance(sizeof(T));
-		return true;
-	}
-
-	/* will fail on signed integers; xsettings has none of those though */
-	template<typename T>
-	bool ReadInt(T& ret) {
-		static_assert(std::is_integral<T>::value);
-
-		if (!ReadData<T>(ret))
-			return false;
-
-		switch (byte_order_) {
-			case LSBFirst:
-				ret = Endian::byteswap_little_to_host(ret);
-				break;
-			case MSBFirst:
-				ret = Endian::byteswap_big_to_host(ret);
-				break;
-			default:
-				/* can't know for sure. punt */
-				return false;
-		}
-
-		return true;
-	}
-
-	bool ReadString(std::string& str, std::size_t size);
-	bool Advance(std::size_t amount);
-
-	/* raw data */
-	std::uint8_t *bytes_ = nullptr;
-	std::size_t offset_ = 0;
-	std::size_t size_ = 0;
+	ByteStream stream;
 
 	/* parsed in the constructor */
-	std::uint8_t byte_order_ = 0;
 	std::uint32_t serial_ = 0;
 	std::uint32_t total_items_ = 0;
 };
@@ -103,95 +62,93 @@
 	return total_items_;
 }
 
-bool Parser::ReadString(std::string& str, std::size_t size) {
-	if (offset_ + size >= size_)
-		return false;
-
-	str.assign(reinterpret_cast<const char *>(bytes_ + offset_), size);
-	Advance(size);
-	return true;
+Parser::Parser(std::uint8_t *bytes, std::size_t size) : stream(bytes, size) {
 }
 
-bool Parser::Advance(std::size_t amount) {
-	if (offset_ + amount >= size_)
+bool Parser::ParseHeader(void) {
+	std::uint8_t byte_order;
+	if (!stream.ReadBinary<std::uint8_t>(byte_order))
 		return false;
 
-	offset_ += amount;
-	return true;
-}
-
-Parser::Parser(std::uint8_t *bytes, std::size_t size) {
-	bytes_ = bytes;
-	size_ = size;
+	switch (byte_order) {
+		case MSBFirst:
+			stream.SetEndianness(ByteStream::ByteOrder::Big);
+			break;
+		case LSBFirst:
+			stream.SetEndianness(ByteStream::ByteOrder::Little);
+			break;
+		default:
+			return false; /* errr */
+	}
 
-	if (!ReadData<std::uint8_t>(byte_order_))
-		return;
+	stream.Advance(3);
 
-	Advance(3);
+	if (!stream.ReadInt<std::uint32_t>(serial_))
+		return false;
 
-	if (!ReadInt<std::uint32_t>(serial_))
-		return;
+	if (!stream.ReadInt<std::uint32_t>(total_items_))
+		return false;
 
-	if (!ReadInt<std::uint32_t>(total_items_))
-		return;
+	return true;
 }
 
 std::optional<SettingsItem> Parser::ParseNextItem(void) {
 	SettingsItem item;
 
 	/* read one byte */
-	if (!ReadInt<std::uint8_t>(item.type))
+	if (!stream.ReadInt<std::uint8_t>(item.type))
 		return std::nullopt;
 
 	if (!item.VerifyType())
 		return std::nullopt;
 
 	/* skip padding */
-	if (!Advance(1))
+	if (!stream.Advance(1))
 		return std::nullopt;
 
 	/* parse the name */
 	std::uint16_t name_size;
-	if (!ReadInt<std::uint16_t>(name_size))
+	if (!stream.ReadInt<std::uint16_t>(name_size))
 		return std::nullopt;
 
-	if (!ReadString(item.name, name_size))
+	if (!stream.ReadString(item.name, name_size))
 		return std::nullopt;
 
 	/* padding */
-	if (!Advance(GetPadding(name_size, 4)))
+	if (!stream.Advance(GetPadding(name_size, 4)))
 		return std::nullopt;
 
-	if (!ReadInt<std::uint32_t>(item.serial))
+	if (!stream.ReadInt<std::uint32_t>(item.serial))
 		return std::nullopt;
 
 	switch (item.type) {
 		case SettingsItem::TypeInt: {
-			if (!ReadInt<std::uint32_t>(item.data.integer))
+			if (!stream.ReadInt<std::int32_t>(item.data.integer))
 				return std::nullopt;
 
 			break;
 		}
 		case SettingsItem::TypeStr: {
 			std::uint32_t size;
-			if (!ReadInt<std::uint32_t>(size))
+			if (!stream.ReadInt<std::uint32_t>(size))
 				return std::nullopt;
 
-			if (!ReadString(item.data.string, size))
+			if (!stream.ReadString(item.data.string, size))
 				return std::nullopt;
 
-			/* padding */
-			if (!Advance(GetPadding(size, 4)))
-				return std::nullopt;
+			/* don't fail if advancing fails on this padding,
+			 * because this causes parsing to fail for strings
+			 * at the end of the data */
+			stream.Advance(GetPadding(size, 4));
 
 			break;
 		}
 		case SettingsItem::TypeRgba: {
-			/* The order here is important!! */
-			if (!ReadInt<std::uint16_t>(item.data.rgba.red)
-				|| !ReadInt<std::uint16_t>(item.data.rgba.blue)
-				|| !ReadInt<std::uint16_t>(item.data.rgba.green)
-				|| !ReadInt<std::uint16_t>(item.data.rgba.alpha))
+			/* it's actually RBGA, but whatever. */
+			if (!stream.ReadInt<std::uint16_t>(item.data.rgba.red)
+				|| !stream.ReadInt<std::uint16_t>(item.data.rgba.blue)
+				|| !stream.ReadInt<std::uint16_t>(item.data.rgba.green)
+				|| !stream.ReadInt<std::uint16_t>(item.data.rgba.alpha))
 				return std::nullopt;
 
 			break;
@@ -204,23 +161,6 @@
 	return item;
 }
 
-std::vector<SettingsItem> Parser::ParseAllItems(void) {
-	offset_ = 0;
-
-	std::uint32_t i;
-	std::vector<SettingsItem> items;
-
-	for (i = 0; i < total_items_; i++) {
-		std::optional<SettingsItem> item = ParseNextItem();
-		if (!item)
-			break;
-
-		items.push_back(item.value());
-	}
-
-	return items;
-}
-
 /* ------------------------------------------------------------------------- */
 /* real X11 code */
 
@@ -323,7 +263,18 @@
 		return false;
 
 	Parser parser(xsettings_raw.data(), xsettings_raw.size());
-	settings = parser.ParseAllItems();
+	if (!parser.ParseHeader())
+		return false;
+
+	std::uint32_t total = parser.GetTotalItems();
+
+	while (total--) {
+		std::optional<SettingsItem> opt_item = parser.ParseNextItem();
+		if (!opt_item)
+			break;
+
+		settings.push_back(opt_item.value());
+	}
 
 	return true;
 }
@@ -334,10 +285,12 @@
 		return false;
 
 	Parser parser(xsettings_raw.data(), xsettings_raw.size());
+	if (!parser.ParseHeader())
+		return false;
 
 	std::uint32_t total = parser.GetTotalItems();
 
-	for (; total; total--) {
+	while (total--) {
 		std::optional<SettingsItem> opt_item = parser.ParseNextItem();
 		if (!opt_item)
 			return false;