Skip to content

Instantly share code, notes, and snippets.

@CobaltXII
Last active June 19, 2021 03:09
Show Gist options
  • Save CobaltXII/0281a30ac50486c61690acbf05eefceb to your computer and use it in GitHub Desktop.
Save CobaltXII/0281a30ac50486c61690acbf05eefceb to your computer and use it in GitHub Desktop.
#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