Merge branch 'TupleConverter' into 'master'

Tuple converter

See merge request C3/C3!200
master
Pawel Kurowski 2020-07-29 14:19:34 +01:00
commit 1a69856b89
6 changed files with 319 additions and 107 deletions

View File

@ -233,7 +233,7 @@ namespace FSecure
/// Function responsible for recursively packing data to ByteVector.
/// @param self. Reference to ByteVector object using TupleHandler.
/// @param t. reference to tuple.
static void Write(ByteVector& self, T const& t)
static void Write([[maybe_unused]] ByteVector& self, [[maybe_unused]] T const& t)
{
if constexpr (N != 0)
{
@ -245,7 +245,7 @@ namespace FSecure
/// Function responsible for recursively calculating buffer size needed for call with tuple argument.
/// @param t. reference to tuple.
/// @return size_t number of bytes needed.
static size_t Size(T const& t)
static size_t Size([[maybe_unused]] T const& t)
{
if constexpr (N != 0)
return ByteVector::Size(std::get<std::tuple_size_v<T> - N>(t)) + TupleHandler<T, N - 1>::Size(t);
@ -256,11 +256,11 @@ namespace FSecure
/// C++ allows cast from pair to tuple of two, but not other way around.
/// This is oversight, because pair is much older concept than tuple, and no proposition was made to expand old type.
/// This function ensures, that TupleHandler always returns requested type, so no cast is necessary.
static T ReadExplicit(ByteView& self)
static auto ReadExplicit(ByteView& self)
{
auto tmp = Read(self);
if constexpr (Utils::IsPair<T>)
return { std::get<0>(tmp), std::get<1>(tmp) };
return std::pair{ std::get<0>(tmp), std::get<1>(tmp) };
else
return tmp;
}
@ -271,7 +271,7 @@ namespace FSecure
{
if constexpr (N != 0)
{
auto current = std::make_tuple(self.Read<std::tuple_element_t<std::tuple_size_v<T> - N, T>>());
auto current = std::make_tuple(self.Read<Utils::RemoveCVR<std::tuple_element_t<std::tuple_size_v<T> - N, T>>>());
auto rest = TupleHandler<T, N - 1>::Read(self);
return std::tuple_cat(current, rest);
}
@ -282,4 +282,175 @@ namespace FSecure
}
};
};
/// @brief Class providing simple way of generating ByteConverter of custom types by treating them as tuple.
/// ByteConverter can use this functionality by inheriting from TupleConverter and providing
/// public static std::tuple<...> Convert(T const&) method.
/// Use Utils::MakeConversionTuple to create efficient tuple of value or references to members.
/// ByteVector can declare its own versions of To/From/Size methods if it needs dedicated logic to serialize type.
/// @tparam T Type for serialization.
template <typename T>
struct TupleConverter
{
private:
/// @brief Type returned by ByteConverter<C>::Convert.
/// ByteConverter<C> specialization may not yet be defined.
/// This type must be deduced late in instantiation procedure.
/// @tparam C Type for serialization.
template <typename C>
using ConvertType = decltype(ByteConverter<C>::Convert(std::declval<C>()));
/// @brief Helper class compatible with Utils::Apply. Checks if size after serialization, of all tuple types, can be determined at compile time.
struct IsSizeConstexpr
{
template <typename ...Ts>
static constexpr auto Apply()
{
return ((ConverterDeduction<Ts>::FunctionSize::value == ConverterDeduction<Ts>::FunctionSize::compileTime) && ...);
}
};
/// @brief Helper class compatible with Utils::Apply. Determines size after serialization, of all tuple types.
struct GetConstexprSize
{
template <typename ...Ts>
static constexpr auto Apply()
{
return ((ByteConverter<Ts>::Size() + ...));
}
};
/// @brief This class is designed for lazy evaluation of ConvertType<T> for IsSizeConstexpr template.
/// If used for SFINAE, ConvertType will break compilation, at not defined Convert function.
/// MSVC is evaluating default template parameter when, and only if, it is used. This behavior results in successful compilation
/// Strict compilers like clang or gcc would evaluate ConvertType before correct ByteConverter specialization is provided.
/// With this template, instantiation point is delayed, until Convert method is reachable in function lookup.
/// @tparam C Type for serialization.
template <typename C>
class IsSizeConstexprLazy
{
template <typename S> static uint16_t test(std::enable_if_t<Utils::Apply<IsSizeConstexpr, ConvertType<S>>::value, int>);
template <typename S> static uint8_t test(...);
public:
static constexpr bool value = sizeof(test<C>(0)) == sizeof(uint16_t);
};
public:
/// @brief Use it to convert raw data into tuple.
/// Allows splitting deserialization into two phases.
/// 1. Retrieve data as tuple. ByteView internal pointer will be correctly moved in the process.
/// 2. Create dedicated logic of transforming tuple into desired type.
/// @param bv. Buffer with serialized data.
/// @return tuple of retrieved data for type construction.
static auto Convert(ByteView& bv)
{
return bv.Read<ConvertType<T>>();
}
// From this point forward will be implemented ByteConverter standard interface methods.
/// @brief Default implementation of To method.
/// Serializes data treating it as tuple generated by Convert.
/// @param obj Object for serialization.
/// @param bv output ByteVector with already allocated memory for data.
static void To(T const& obj, ByteVector& bv)
{
bv.Store(ByteConverter<T>::Convert(obj));
}
/// @brief Default implementation of From method.
/// Retrieves data from view and creates new object by passing tuple as arguments for T{...} construction.
/// This implementation uses brace enclosed construction, becouse std::make_from_tuple does not support trivial types.
/// Bear in mind that construction with parentheses, and with braces, is not interchangeable.
/// @param bv. Buffer with serialized data.
/// @return constructed type.
static T From(ByteView& bv)
{
return std::apply(Utils::Construction::Braces<T>{}, Convert(bv));
}
/// @brief Default implementation of Size method with compile time evaluation.
/// @note All template parameters are used only to determine if function should be defined.
/// @return size_t. Number of bytes used after serialization.
template <typename C = T, std::enable_if_t<IsSizeConstexprLazy<C>::value, int> = 0>
static constexpr size_t Size()
{
return Utils::Apply<GetConstexprSize, ConvertType<T>>::value;
}
/// @brief Default implementation of Size method.
/// @param obj Object for serialization.
/// @note All template parameters are used only to determine if function should be defined.
/// @return size_t. Number of bytes used after serialization.
template <typename C = T, std::enable_if_t<!IsSizeConstexprLazy<C>::value, int> = 0>
static size_t Size(T const& obj)
{
return ByteVector::Size(ByteConverter<T>::Convert(obj));
}
};
/// @brief Class providing simple way of generating ByteConverter listing only necessary members once.
/// ByteConverter can use this functionality by inheriting from PointerTupleConverter and providing
/// public static std::tuple<T::*...> MemberPointers() method.
/// Use std::make_tuple to create return value out of member list.
/// ByteVector can declare its own versions of To/From/Size methods if it needs dedicated logic to serialize type.
/// @tparam T Type for serialization.
template <typename T>
struct PointerTupleConverter : TupleConverter<T>
{
private:
/// @brief Class applying pointers to members to object.
/// Compatible with std::apply.
/// Creates TupleConverter<T>::ConvertType object.
class ReferenceMembers
{
T const& m_Obj;
public:
ReferenceMembers(T const& obj) : m_Obj{ obj } {}
template <typename ... Ts>
auto operator () (Ts T::*...ts) const
{
return Utils::MakeConversionTuple(m_Obj.*ts ...);
}
};
/// @brief Function assigning object members with tuple of values using tuple member pointers.
/// @tparam PtrTpl Tuple of pointers to members type.
/// @tparam ValueTpl Tuple of values type.
/// @tparam Is Index sequence used to match tuples one to one.
/// @param obj Object to be assigned.
/// @param ptrTpl Tuple of pointers to members.
/// @param valueTpl Tuple of values.
template <typename PtrTpl, typename ValueTpl, size_t ...Is>
static void AssignToMembers(T& obj, PtrTpl const& ptrTpl, ValueTpl valueTpl, std::index_sequence<Is...>)
{
((obj.*(std::get<Is>(ptrTpl)) = std::move(std::get<Is>(valueTpl))), ...);
}
public:
/// @brief Default implementation of Convert method used by TupleConverter for serialization.
/// Function uses ByteConverter<T>::MemberPointers() to reference T object members.
/// @param obj object to be serialized.
/// @return TupleConverter<T>::ConvertType values to be serialized.
static auto Convert(T const& obj)
{
auto ptrTpl = ByteConverter<T>::MemberPointers();
return std::apply(ReferenceMembers{ obj }, ptrTpl);
}
/// @brief Shadowed TupleConverter<T>::From initalizing only selected members, skipped ones will be default initialized.
/// @note T must have default constructor.
/// @param bv. Buffer with serialized data.
/// @return constructed type.
static T From(ByteView& bv)
{
auto ret = T{};
auto ptrTpl = ByteConverter<T>::MemberPointers();
auto valueTpl = TupleConverter<T>::Convert(bv);
AssignToMembers(ret, ptrTpl, std::move(valueTpl), std::make_index_sequence<std::tuple_size<decltype(ptrTpl)>::value>{});
return ret;
}
};
}

View File

@ -55,6 +55,13 @@ namespace FSecure
static constexpr bool value = ((ConverterDeduction<Ts>::FunctionTo::value != ConverterDeduction<Ts>::FunctionTo::absent) && ...);
};
/// Check if type can be concatenated
template <typename ...Ts>
struct ConcatCondition
{
static constexpr bool value = (sizeof...(Ts) > 0) && ((Utils::IsOneOf<Ts, ByteView, ByteVector>::value && ...));
};
/// An owning container.
class ByteVector : std::vector<std::uint8_t>
{
@ -181,7 +188,7 @@ namespace FSecure
/// Does not write header with size..
/// @param args. Objects to be stored.
/// @return itself to allow chaining.
template <typename ...Ts, typename = std::enable_if_t<(sizeof...(Ts) > 0) && ((Utils::IsOneOf<Ts, ByteView, ByteVector>::value && ...))>>
template <typename ...Ts, typename std::enable_if_t<ConcatCondition<Ts...>::value, int> = 0>
ByteVector& Concat(Ts const& ...args)
{
auto oldSize = size();
@ -207,7 +214,7 @@ namespace FSecure
template <typename T, typename ...Ts, typename std::enable_if_t<WriteCondition<T, Ts...>::value, int> = 0>
static ByteVector Create(T const& arg, Ts const& ...args)
{
return ByteVector{}.Write(arg, args...);
return std::move(ByteVector{}.Write(arg, args...));
}
/// Calculate the size that the argument will take in memory
@ -215,9 +222,9 @@ namespace FSecure
/// @param args. Rest of types that will be handled with recursion.
/// @return size_t number of bytes needed.
template<typename T, typename ...Ts>
static size_t Size(T const& arg, Ts const& ...args)
static size_t Size([[maybe_unused]] T const& arg, Ts const& ...args)
{
if constexpr (sizeof...(Ts) != 0)
if constexpr (sizeof...(args) != 0)
return Size(arg) + Size(args...);
else if constexpr (ConverterDeduction<T>::FunctionSize::value == ConverterDeduction<T>::FunctionSize::compileTime)
return ByteConverter<T>::Size();
@ -258,6 +265,10 @@ namespace FSecure
/// Declaration of friendship.
template <typename , typename>
friend struct ByteConverter;
/// Declaration of friendship.
template <typename>
friend struct TupleConverter;
};
namespace Literals

View File

@ -193,24 +193,6 @@ namespace FSecure
/// ByteReader will modify ByteView used for it construction.
ByteView& m_byteView;
/// Declaration of base template used for pointer to member type deduction.
template<class T>
struct SplitMemberPointer;
/// Specialization that will perform type deduction.
template<class C, class T>
struct SplitMemberPointer<T C::*> {
using type = T;
using declaringType = C;
};
/// Function used to call constructor of type T with tuple of types matching constructor arguments.
template<typename T, typename Tpl, size_t... Is>
static T TupleToConstructor(Tpl&& tpl, std::integer_sequence<size_t, Is...>)
{
return T{ std::get<Is>(std::move(tpl))... };
}
public:
/// Create ByteReader.
/// @param bv, ByteView with data to read.
@ -225,27 +207,6 @@ namespace FSecure
{
((ts = m_byteView.Read<decltype(ts)>()), ...);
}
/// Create object by reading provided types, and passing them to object constructor.
/// @tparam T, type to be constructed.
/// @tparam Ts, types to be read from ByteView, and passed as T constructor arguments.
/// @note T is not the same as first template parameter of Create(...).
template <typename T, typename ...Ts>
auto Create() -> decltype(T{ std::declval<Ts>()... })
{
return TupleToConstructor<T>(m_byteView.Read<Ts...>(), std::make_index_sequence<sizeof...(Ts)>{});
}
/// Create object by reading provided types deduced from pointers to members
/// @tparam T, first pointer to member. Ensures that at least one object will be read from ByteView
/// @tparam Ts, rest of pointers to members.
/// @note T is not the same as first template parameter of Create(void).
template <typename T, typename ...Ts>
auto Create(T, Ts...) -> decltype(typename SplitMemberPointer<T>::declaringType{ std::declval<typename SplitMemberPointer<T>::type>(), std::declval<typename SplitMemberPointer<Ts>::type>()... })
{
return TupleToConstructor<typename SplitMemberPointer<T>::declaringType>(m_byteView.Read<typename SplitMemberPointer<T>::type, typename SplitMemberPointer<Ts>::type...>(), std::make_index_sequence<1 + sizeof...(Ts)>{});
}
};
namespace Utils

View File

@ -6,6 +6,7 @@
#include <functional>
#include <array>
#include <vector>
#include <tuple>
#ifndef OBF
# define OBF(x) x
@ -229,4 +230,110 @@ namespace FSecure::Utils
enum { value = (sizeof(test<T>(0)) - sizeof(uint8_t)) };
};
}
/// Namespace for internal implementation
namespace Detail
{
/// @brief Default implementation of class checking if it is worth to take reference or copy by value
/// Represents false.
template<typename T, typename Enable>
struct WorthAddingConstRefImpl : std::false_type
{};
/// @brief Specialization of class checking if it is worth to take reference or copy by value.
/// Represents true.
template<typename T>
struct WorthAddingConstRefImpl<T,
std::enable_if_t<
!std::is_rvalue_reference_v<T> && (!std::is_trivial_v<std::remove_reference_t<T>> || (sizeof(T) > sizeof(long long)))
>
> : std::true_type{};
/// @brief Default implementation of class declaring simpler type to use based on T type.
/// Represents copy by value.
template<typename T, typename Enable>
struct AddConstRefToNonTrivialImpl
{
using type = std::remove_reference_t<T>;
};
/// @brief Specialization of class declaring simpler type to use based on T type.
/// Represents copy by reference.
template<typename T>
struct AddConstRefToNonTrivialImpl<T, std::enable_if_t<WorthAddingConstRefImpl<T, void>::value>>
{
using type = std::remove_reference_t<T> const&;
};
}
/// @brief Class checking if it is worth to take reference or copy by value.
template<typename T>
struct WorthAddingConstRef : Detail::WorthAddingConstRefImpl<T, void> {};
/// @brief Simplified WorthAddingConstRef<T>::value.
template<typename T>
static constexpr auto WorthAddingConstRefV = WorthAddingConstRef<T>::value;
/// @brief Class declaring simpler type to use based on T type.
template<typename T>
struct AddConstRefToNonTrivial : Detail::AddConstRefToNonTrivialImpl<T, void> {};
/// @brief Simplified AddConstRefToNonTrivial<T>::type.
template<typename T>
using AddConstRefToNonTrivialT = typename AddConstRefToNonTrivial<T>::type;
/// @brief Helper allowing transformation of provided arguments to tuple of values/references that are used for serialization.
/// @param ...args arguments to be stored in tuple
/// @return tuple with references to non trivial types, and values of simple ones.
template<typename ...Args>
auto MakeConversionTuple(Args&& ...args)
{
return std::tuple<AddConstRefToNonTrivialT<Args&&>...>(std::forward<Args>(args)...);
}
/// Construction with parentheses and with braces is not interchangeable.
/// std::make_from_tuple must use constructor and is unable to create trivial type.
/// Types implemented in this namespace, alongside with std::apply, allows us choose method used for construction.
/// For more information look at:
/// https://groups.google.com/a/isocpp.org/forum/#!topic/std-discussion/aQQzL0JoXLg
namespace Construction
{
/// @brief Type used for construction with braces.
template <typename T>
struct Braces
{
template <typename ... As>
constexpr auto operator () (As&& ... as) const
{
return T{ std::forward<As>(as)... };
}
};
/// @brief Type used for construction with parentheses.
template <typename T>
struct Parentheses
{
template <typename ... As>
constexpr auto operator () (As&& ... as) const
{
return T(std::forward<As>(as)...);
}
};
}
/// @brief Constexpr helper to perform logic on tuple types. Evaluation result is assigned to value member.
/// @tparam T Class with function to be applied. Must define template<typename...> constexpr auto Apply(). Tuple types will be passed by parameter pack.
/// @tparam Tpl Tuple with types on which logic will be applied.
template <typename T, typename Tpl>
struct Apply
{
private:
template <size_t ...Is>
constexpr static auto ApplyImpl(std::index_sequence<Is...>)
{
return T::template Apply<std::tuple_element_t<Is, Tpl>...>();
}
public:
constexpr static auto value = ApplyImpl(std::make_index_sequence<std::tuple_size<Tpl>::value>{});
};
}

View File

@ -36,63 +36,35 @@ namespace FSecure
/// overload ByteConverter for RTL_OSVERSIONINFOEXW. szCSDVersion and wSuiteMask are omitted.
template<>
struct ByteConverter<RTL_OSVERSIONINFOEXW>
struct ByteConverter<RTL_OSVERSIONINFOEXW> : PointerTupleConverter<RTL_OSVERSIONINFOEXW>
{
/// Serialize HostInfo type to ByteVector.
/// Serialization of RTL_OSVERSIONINFOEXW type to/from ByteVector.
/// @param obj. Object to be serialized.
/// @param bv. ByteVector to be expanded.
static void To(RTL_OSVERSIONINFOEXW const& obj, ByteVector& bv)
static auto MemberPointers()
{
bv.Store(obj.dwOSVersionInfoSize, obj.dwMajorVersion, obj.dwMinorVersion, obj.dwBuildNumber, obj.dwPlatformId, obj.wServicePackMajor, obj.wServicePackMinor, obj.wProductType);
}
/// Get size required after serialization.
/// @param obj. Object to be serialized.
/// @return size_t. Number of bytes used after serialization.
static size_t Size()
{
RTL_OSVERSIONINFOEXW* p = nullptr;
return ByteVector::Size(p->dwOSVersionInfoSize, p->dwMajorVersion, p->dwMinorVersion, p->dwBuildNumber, p->dwPlatformId, p->wServicePackMajor, p->wServicePackMinor, p->wProductType);
}
/// Deserialize from ByteView.
/// @param bv. Buffer with serialized data.
/// @return RTL_OSVERSIONINFOEXW.
static RTL_OSVERSIONINFOEXW From(ByteView& bv)
{
RTL_OSVERSIONINFOEXW obj = {0,};
ByteReader{ bv }.Read(obj.dwOSVersionInfoSize, obj.dwMajorVersion, obj.dwMinorVersion, obj.dwBuildNumber, obj.dwPlatformId, obj.wServicePackMajor, obj.wServicePackMinor, obj.wProductType);
return obj;
using T = RTL_OSVERSIONINFOEXW;
return std::make_tuple(
&T::dwOSVersionInfoSize,
&T::dwMajorVersion,
&T::dwMinorVersion,
&T::dwBuildNumber,
&T::dwPlatformId,
&T::wServicePackMajor,
&T::wServicePackMinor,
&T::wProductType
);
}
};
/// overload ByteConverter for HostInfo
template<>
struct ByteConverter<HostInfo>
struct ByteConverter<HostInfo> : TupleConverter<HostInfo>
{
/// Serialize HostInfo type to ByteVector.
/// Serialization of HostInfo type to/from ByteVector.
/// @param obj. Object to be serialized.
/// @param bv. ByteVector to be expanded.
static void To(HostInfo const& obj, ByteVector& bv)
static auto Convert(HostInfo const& obj)
{
bv.Store(obj.m_ComputerName, obj.m_UserName, obj.m_Domain, obj.m_OsVersionInfo, obj.m_ProcessId, obj.m_IsElevated);
}
/// Get size required after serialization.
/// @param obj. Object to be serialized.
/// @return size_t. Number of bytes used after serialization.
static size_t Size(HostInfo const& obj)
{
return ByteVector::Size(obj.m_ComputerName, obj.m_UserName, obj.m_Domain, obj.m_OsVersionInfo, obj.m_ProcessId, obj.m_IsElevated);
}
/// Deserialize from ByteView.
/// @param bv. Buffer with serialized data.
/// @return arithmetic type.
static HostInfo From(ByteView& bv)
{
using T = HostInfo;
return ByteReader{ bv }.Create(&T::m_ComputerName, &T::m_UserName, &T::m_Domain, &T::m_OsVersionInfo, &T::m_ProcessId, &T::m_IsElevated);
return Utils::MakeConversionTuple(obj.m_ComputerName, obj.m_UserName, obj.m_Domain, obj.m_OsVersionInfo, obj.m_ProcessId, obj.m_IsElevated);
}
};
}

View File

@ -78,21 +78,11 @@ namespace FSecure
{
/// Specialize ByteConverter for RouteId.
template <>
struct ByteConverter <C3::RouteId>
struct ByteConverter<C3::RouteId> : TupleConverter<C3::RouteId>
{
static void To(C3::RouteId const& obj, ByteVector& bv)
static auto Convert(C3::RouteId const& obj)
{
bv.Store(obj.GetAgentId(), obj.GetInterfaceId());
}
constexpr static size_t Size()
{
return C3::RouteId::BinarySize;
}
static C3::RouteId From(ByteView& bv)
{
return ByteReader{ bv }.Create<C3::RouteId, decltype(std::declval<C3::RouteId>().GetAgentId()), decltype(std::declval<C3::RouteId>().GetInterfaceId())>();
return Utils::MakeConversionTuple(obj.GetAgentId(), obj.GetInterfaceId());
}
};