Last active
June 19, 2021 03:09
-
-
Save CobaltXII/0281a30ac50486c61690acbf05eefceb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <array> | |
#include <cstddef> | |
#include <cstdint> | |
#include <cstring> | |
#include <map> | |
#include <string> | |
#include <tuple> | |
#include <type_traits> | |
#include <utility> | |
#include <vector> | |
// A message. | |
typedef std::vector<std::uint8_t> Message; | |
// Checks whether T is a full specialization of std::pair. Provides the | |
// member constant value which is equal to true, if T is a full | |
// full specialization of std::pair. Otherwise, value is equal to false. | |
template<class T> | |
struct is_pair: std::false_type {}; | |
// A full specialization of std::pair. | |
template<class T1, class T2> | |
struct is_pair<std::pair<T1, T2>>: std::true_type {}; | |
// Helper variable template. | |
template<class T> | |
inline constexpr bool is_pair_v = is_pair<T>::value; | |
// Checks whether T is a full specialization of std::tuple. Provides the | |
// member constant value which is equal to true, if T is a full | |
// full specialization of std::tuple. Otherwise, value is equal to false. | |
template<class T> | |
struct is_tuple: std::false_type {}; | |
// A full specialization of std::tuple. | |
template<class... Types> | |
struct is_tuple<std::tuple<Types...>>: std::true_type {}; | |
// Helper variable template. | |
template<class T> | |
inline constexpr bool is_tuple_v = is_tuple<T>::value; | |
// Checks whether T is a full specialization of std::array. Provides the | |
// member constant value which is equal to true, if T is a full | |
// specialization of std::array. Otherwise, value is equal to false. | |
template<class T> | |
struct is_array: std::false_type {}; | |
// A full specialization of std::array. | |
template<class T, std::size_t N> | |
struct is_array<std::array<T, N>>: std::true_type {}; | |
// Helper variable template. | |
template<class T> | |
inline constexpr bool is_array_v = is_array<T>::value; | |
// Checks whether T is a full specialization of std::basic_string. | |
// Provides the member constant value which is equal to true, if T is a | |
// full specialization of std::basic_string. Otherwise, value is | |
// equal to false. | |
template<class T> | |
struct is_basic_string: std::false_type {}; | |
// A full specialization of std::basic_string. | |
template<class CharT, class Traits, class Allocator> | |
struct is_basic_string<std::basic_string<CharT, Traits, Allocator>>: std::true_type {}; | |
// Helper variable template. | |
template<class T> | |
inline constexpr bool is_basic_string_v = is_basic_string<T>::value; | |
// Checks whether T is a full specialization of std::vector. Provides the | |
// member constant value which is equal to true, if T is a full | |
// specialization of std::vector. Otherwise, value is equal to false. | |
template<class T> | |
struct is_vector: std::false_type {}; | |
// A full specialization of std::vector. | |
template<class T, class Allocator> | |
struct is_vector<std::vector<T, Allocator>>: std::true_type {}; | |
// Helper variable template. | |
template<class T> | |
inline constexpr bool is_vector_v = is_vector<T>::value; | |
// Checks whether T is a full specialization of std::map. Provides the | |
// member constant value which is equal to true, if T is a full | |
// specialization of std::map. Otherwise, value is equal to false. | |
template<class T> | |
struct is_map: std::false_type {}; | |
// A full specialization of std::map. | |
template<class Key, class T, class Compare, class Allocator> | |
struct is_map<std::map<Key, T, Compare, Allocator>>: std::true_type {}; | |
// Helper variable template. | |
template<class T> | |
inline constexpr bool is_map_v = is_map<T>::value; | |
// A message encoder. | |
template<std::uint8_t Mask = 0, typename Size = std::uint64_t> | |
class MessageEncoder { | |
private: | |
// The message. | |
Message& message; | |
// Encode a byte. | |
void EncodeByte(std::uint8_t value) { | |
message.push_back(value ^ Mask); | |
} | |
public: | |
// Create a message encoder. | |
MessageEncoder(Message& message): message(message) {} | |
// Encode an integral value. The least significant byte is encoded | |
// first and the most significant byte is encoded last (little-endian | |
// representation). Boolean values are encoded using 8 bits. | |
template<typename T> | |
std::enable_if_t<std::is_integral_v<T>> Encode(const T& value) { | |
if constexpr (std::is_same_v<T, bool>) { | |
Encode<std::uint8_t>(value); | |
} else { | |
for (std::size_t i = 0; i < sizeof(T); i++) { | |
EncodeByte(value >> (i * 8) & 0xFF); | |
} | |
} | |
} | |
// Encode a floating-point value. The value is represented as it is on | |
// the host machine. | |
template<typename T> | |
std::enable_if_t<std::is_floating_point_v<T>> Encode(const T& value) { | |
const std::uint8_t* ptr = reinterpret_cast<const std::uint8_t*>(&value); | |
for (std::size_t i = 0; i < sizeof(T); i++) { | |
EncodeByte(ptr[i]); | |
} | |
} | |
// Encode an enum. | |
template<typename T> | |
std::enable_if_t<std::is_enum_v<T>> Encode(const T& value) { | |
Encode(static_cast<std::underlying_type_t<T>>(value)); | |
} | |
// Encode a std::pair or a std::tuple. | |
template<typename T> | |
std::enable_if_t<is_pair_v<T> || is_tuple_v<T>> Encode(const T& values) { | |
std::apply([&](const auto&... value) { | |
(Encode(value), ...); | |
}, values); | |
} | |
// Encode a std::array. | |
template<typename T> | |
std::enable_if_t<is_array_v<T>> Encode(const T& values) { | |
for (const auto& value: values) { | |
Encode(value); | |
} | |
} | |
// Encode a std::basic_string or a std::vector. The number of values is | |
// encoded before the values themselves. | |
template<typename T> | |
std::enable_if_t<is_basic_string_v<T> || is_vector_v<T>> Encode(const T& values) { | |
Encode<Size>(values.size()); | |
for (const auto& value: values) { | |
Encode(value); | |
} | |
} | |
// Encode a std::map. The number of values is encoded before the values | |
// themselves. | |
template<typename T> | |
std::enable_if_t<is_map_v<T>> Encode(const T& values) { | |
Encode<Size>(values.size()); | |
for (const auto& value: values) { | |
Encode(value.first); | |
Encode(value.second); | |
} | |
} | |
}; | |
// A message decoder. | |
template<std::uint8_t Mask = 0, typename Size = std::uint64_t> | |
class MessageDecoder { | |
private: | |
// The message. | |
const Message& message; | |
// The current offset. | |
std::size_t offset = 0; | |
// Decode a byte. | |
std::uint8_t DecodeByte() { | |
return message.at(offset++) ^ Mask; | |
} | |
// Get the size of an integral or floating-point value starting from | |
// the given offset. | |
template<typename T> | |
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, std::size_t> SizeOf(std::size_t offset) { | |
if constexpr (std::is_same_v<T, bool>) { | |
return SizeOf<std::uint8_t>(offset); | |
} else { | |
return sizeof(T); | |
} | |
} | |
// Get the size of an enum starting from the given offset. | |
template<typename T> | |
std::enable_if_t<std::is_enum_v<T>, std::size_t> SizeOf(std::size_t offset) { | |
return SizeOf<std::underlying_type_t<T>>(offset); | |
} | |
// Get the size of a std::pair or a std::tuple starting from the given | |
// offset. | |
template<typename T> | |
std::enable_if_t<is_pair_v<T> || is_tuple_v<T>, std::size_t> SizeOf(std::size_t offset) { | |
return std::apply([&](auto... types) { | |
return (SizeOfAndIncrement<decltype(types)>(offset) + ...); | |
}, T()); | |
} | |
// Get the size of a std::array starting from the given offset. | |
template<typename T> | |
std::enable_if_t<is_array_v<T>, std::size_t> SizeOf(std::size_t offset) { | |
std::size_t sum = 0; | |
for (std::size_t i = 0; i < std::tuple_size_v<T>; i++) { | |
sum += SizeOfAndIncrement<typename T::value_type>(offset); | |
} | |
return sum; | |
} | |
// Get the size of a std::basic_string or a std::vector starting from | |
// the given offset. | |
template<typename T> | |
std::enable_if_t<is_basic_string_v<T> || is_vector_v<T>, std::size_t> SizeOf(std::size_t offset) { | |
std::size_t sum = SizeOf<Size>(offset); | |
std::size_t size = Peek<Size>(offset); | |
for (std::size_t i = 0; i < size; i++) { | |
sum += SizeOfAndIncrement<typename T::value_type>(offset); | |
} | |
return sum; | |
} | |
// Get the size of a std::map starting from the given offset. | |
template<typename T> | |
std::enable_if_t<is_map_v<T>, std::size_t> SizeOf(std::size_t offset) { | |
std::size_t sum = SizeOf<Size>(offset); | |
std::size_t size = Peek<Size>(offset); | |
for (std::size_t i = 0; i < size; i++) { | |
sum += SizeOfAndIncrement<typename T::key_type>(offset) + SizeOfAndIncrement<typename T::mapped_type>(offset); | |
} | |
return sum; | |
} | |
// Get the size of a type starting from the given offset and increment | |
// the given offset. | |
template<typename T> | |
std::size_t SizeOfAndIncrement(std::size_t& offset) { | |
std::size_t value = SizeOf<T>(offset); | |
offset += value; | |
return value; | |
} | |
// Peek a byte starting from the given offset. | |
std::uint8_t PeekByte(std::size_t& offset) { | |
return message.at(offset++) ^ Mask; | |
} | |
// Peek an integral value starting from the given offset and increment | |
// the given offset. | |
template<typename T> | |
std::enable_if_t<std::is_integral_v<T>, T> Peek(std::size_t& offset) { | |
if constexpr (std::is_same_v<T, bool>) { | |
return Peek<std::uint8_t>(offset); | |
} else { | |
T value = 0; | |
for (std::size_t i = 0; i < sizeof(T); i++) { | |
value |= static_cast<T>(PeekByte(offset)) << (i * 8); | |
} | |
return value; | |
} | |
} | |
// Peek a floating-point value starting from the given offset and | |
// increment the given offset. | |
template<typename T> | |
std::enable_if_t<std::is_floating_point_v<T>, T> Peek(std::size_t& offset) { | |
T value = 0; | |
std::uint8_t* ptr = reinterpret_cast<std::uint8_t*>(&value); | |
for (std::size_t i = 0; i < sizeof(T); i++) { | |
ptr[i] = PeekByte(offset); | |
} | |
return value; | |
} | |
// Peek an enum starting from the given offset and increment the given | |
// offset. | |
template<typename T> | |
std::enable_if_t<std::is_enum_v<T>, T> Peek(std::size_t& offset) { | |
return static_cast<T>(Peek<std::underlying_type_t<T>>(offset)); | |
} | |
// Peek a std::pair or a std::tuple starting from the given offset and | |
// increment the given offset. | |
template<typename T> | |
std::enable_if_t<is_pair_v<T> || is_tuple_v<T>, T> Peek(std::size_t& offset) { | |
T values; | |
std::apply([&](auto&... values) { | |
((values = Peek<std::remove_reference_t<decltype(values)>>(offset)), ...); | |
}, values); | |
return values; | |
} | |
// Peek a std::array starting from the given offset and increment the | |
// given offset. | |
template<typename T> | |
std::enable_if_t<is_array_v<T>, T> Peek(std::size_t& offset) { | |
T values; | |
for (auto& value: values) { | |
value = Peek<typename T::value_type>(offset); | |
} | |
return values; | |
} | |
// Peek a std::basic_string or a std::vector starting from the given | |
// offset and increment the given offset. | |
template<typename T> | |
std::enable_if_t<is_basic_string_v<T> || is_vector_v<T>, T> Peek(std::size_t& offset) { | |
T values; | |
std::size_t size = Peek<Size>(offset); | |
values.reserve(size); | |
for (std::size_t i = 0; i < size; i++) { | |
values.push_back(Peek<typename T::value_type>(offset)); | |
} | |
return values; | |
} | |
// Peek a std::map starting from the given offset and increment the | |
// given offset. | |
template<typename T> | |
std::enable_if_t<is_map_v<T>, T> Peek(std::size_t& offset) { | |
T values; | |
std::size_t size = Peek<Size>(offset); | |
for (std::size_t i = 0; i < size; i++) { | |
values.insert({Peek<typename T::key_type>(offset), Peek<typename T::mapped_type>(offset)}); | |
} | |
return values; | |
} | |
// Check if an integral type, floating-point type, enum, or std::array can | |
// be decoded starting from the given offset. | |
template<typename T> | |
std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T> || std::is_enum_v<T> || is_array_v<T>, bool> CanDecode(std::size_t offset) { | |
return message.size() - offset >= SizeOf<T>(offset); | |
} | |
// Check if a std::pair or a std::tuple can be decoded starting from | |
// the given offset. | |
template<typename T> | |
std::enable_if_t<is_pair_v<T> || is_tuple_v<T>, bool> CanDecode(std::size_t offset) { | |
// Use a dummy value. | |
return std::apply([&](auto... types) { | |
return (CanDecodeAndIncrement<decltype(types)>(offset) && ...); | |
}, T()); | |
} | |
// Check if a std::basic_string or a std::vector can be decoded | |
// starting from the given offset. | |
template<typename T> | |
std::enable_if_t<is_basic_string_v<T> || is_vector_v<T>, bool> CanDecode(std::size_t offset) { | |
// Make sure the size can be decoded. | |
if (!CanDecode<Size>(offset)) { | |
return false; | |
} | |
// Peek the size. | |
std::size_t size = Peek<Size>(offset); | |
// Iterate over the values. | |
for (std::size_t i = 0; i < size; i++) { | |
// Make sure the value can be decoded. | |
if (!CanDecodeAndIncrement<typename T::value_type>(offset)) { | |
return false; | |
} | |
} | |
// All the values can be decoded. | |
return true; | |
} | |
// Check if a std::map can be decoded starting from the given offset. | |
template<typename T> | |
std::enable_if_t<is_map_v<T>, bool> CanDecode(std::size_t offset) { | |
// Make sure the size can be decoded. | |
if (!CanDecode<Size>(offset)) { | |
return false; | |
} | |
// Peek the size. | |
std::size_t size = Peek<Size>(offset); | |
// Iterate over the values. | |
for (std::size_t i = 0; i < size; i++) { | |
// Make sure the value can be decoded. | |
if (!CanDecodeAndIncrement<typename T::key_type>(offset) || !CanDecodeAndIncrement<typename T::mapped_type>(offset)) { | |
return false; | |
} | |
} | |
// All the values can be decoded. | |
return true; | |
} | |
// Check if a type can be decoded starting from the given offset and | |
// increment the given offset. | |
template<typename T> | |
bool CanDecodeAndIncrement(std::size_t& offset) { | |
bool value = CanDecode<T>(offset); | |
offset += SizeOf<T>(offset); | |
return value; | |
} | |
public: | |
// Create a message decoder. | |
MessageDecoder(const Message& message): message(message) {} | |
// Check if a value can be decoded. | |
template<typename T> | |
bool CanDecode() { | |
return CanDecode<T>(offset); | |
} | |
// Decode a value. | |
template<typename T> | |
T Decode() { | |
return Peek<T>(offset); | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment