diff --git a/Src/Common/FSecure/CppTools/ByteConverter/ByteConverter.h b/Src/Common/FSecure/CppTools/ByteConverter/ByteConverter.h index 50c7c2c..db1ad50 100644 --- a/Src/Common/FSecure/CppTools/ByteConverter/ByteConverter.h +++ b/Src/Common/FSecure/CppTools/ByteConverter/ByteConverter.h @@ -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 - N>(t)) + TupleHandler::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) - 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 - N, T>>()); + auto current = std::make_tuple(self.Read - N, T>>>()); auto rest = TupleHandler::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 + struct TupleConverter + { + private: + /// @brief Type returned by ByteConverter::Convert. + /// ByteConverter specialization may not yet be defined. + /// This type must be deduced late in instantiation procedure. + /// @tparam C Type for serialization. + template + using ConvertType = decltype(ByteConverter::Convert(std::declval())); + + /// @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 + static constexpr auto Apply() + { + return ((ConverterDeduction::FunctionSize::value == ConverterDeduction::FunctionSize::compileTime) && ...); + } + }; + + /// @brief Helper class compatible with Utils::Apply. Determines size after serialization, of all tuple types. + struct GetConstexprSize + { + template + static constexpr auto Apply() + { + return ((ByteConverter::Size() + ...)); + } + }; + + /// @brief This class is designed for lazy evaluation of ConvertType 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 + class IsSizeConstexprLazy + { + template static uint16_t test(std::enable_if_t>::value, int>); + template static uint8_t test(...); + + public: + static constexpr bool value = sizeof(test(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>(); + } + + // 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::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{}, 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 ::value, int> = 0> + static constexpr size_t Size() + { + return Utils::Apply>::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 ::value, int> = 0> + static size_t Size(T const& obj) + { + return ByteVector::Size(ByteConverter::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 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 + struct PointerTupleConverter : TupleConverter + { + private: + /// @brief Class applying pointers to members to object. + /// Compatible with std::apply. + /// Creates TupleConverter::ConvertType object. + class ReferenceMembers + { + T const& m_Obj; + public: + ReferenceMembers(T const& obj) : m_Obj{ obj } {} + + template + 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 + static void AssignToMembers(T& obj, PtrTpl const& ptrTpl, ValueTpl valueTpl, std::index_sequence) + { + ((obj.*(std::get(ptrTpl)) = std::move(std::get(valueTpl))), ...); + } + + public: + /// @brief Default implementation of Convert method used by TupleConverter for serialization. + /// Function uses ByteConverter::MemberPointers() to reference T object members. + /// @param obj object to be serialized. + /// @return TupleConverter::ConvertType values to be serialized. + static auto Convert(T const& obj) + { + auto ptrTpl = ByteConverter::MemberPointers(); + return std::apply(ReferenceMembers{ obj }, ptrTpl); + } + + /// @brief Shadowed TupleConverter::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::MemberPointers(); + auto valueTpl = TupleConverter::Convert(bv); + AssignToMembers(ret, ptrTpl, std::move(valueTpl), std::make_index_sequence::value>{}); + return ret; + } + }; } diff --git a/Src/Common/FSecure/CppTools/ByteConverter/ByteVector.h b/Src/Common/FSecure/CppTools/ByteConverter/ByteVector.h index 63c526d..cdfe7be 100644 --- a/Src/Common/FSecure/CppTools/ByteConverter/ByteVector.h +++ b/Src/Common/FSecure/CppTools/ByteConverter/ByteVector.h @@ -55,6 +55,13 @@ namespace FSecure static constexpr bool value = ((ConverterDeduction::FunctionTo::value != ConverterDeduction::FunctionTo::absent) && ...); }; + /// Check if type can be concatenated + template + struct ConcatCondition + { + static constexpr bool value = (sizeof...(Ts) > 0) && ((Utils::IsOneOf::value && ...)); + }; + /// An owning container. class ByteVector : std::vector { @@ -181,7 +188,7 @@ namespace FSecure /// Does not write header with size.. /// @param args. Objects to be stored. /// @return itself to allow chaining. - template 0) && ((Utils::IsOneOf::value && ...))>> + template ::value, int> = 0> ByteVector& Concat(Ts const& ...args) { auto oldSize = size(); @@ -207,7 +214,7 @@ namespace FSecure template ::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 - 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::FunctionSize::value == ConverterDeduction::FunctionSize::compileTime) return ByteConverter::Size(); @@ -258,6 +265,10 @@ namespace FSecure /// Declaration of friendship. template friend struct ByteConverter; + + /// Declaration of friendship. + template + friend struct TupleConverter; }; namespace Literals diff --git a/Src/Common/FSecure/CppTools/ByteConverter/ByteView.h b/Src/Common/FSecure/CppTools/ByteConverter/ByteView.h index 1eab74e..ccbd032 100644 --- a/Src/Common/FSecure/CppTools/ByteConverter/ByteView.h +++ b/Src/Common/FSecure/CppTools/ByteConverter/ByteView.h @@ -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 - struct SplitMemberPointer; - - /// Specialization that will perform type deduction. - template - struct SplitMemberPointer { - using type = T; - using declaringType = C; - }; - - /// Function used to call constructor of type T with tuple of types matching constructor arguments. - template - static T TupleToConstructor(Tpl&& tpl, std::integer_sequence) - { - return T{ std::get(std::move(tpl))... }; - } - public: /// Create ByteReader. /// @param bv, ByteView with data to read. @@ -225,27 +207,6 @@ namespace FSecure { ((ts = m_byteView.Read()), ...); } - - /// 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 - auto Create() -> decltype(T{ std::declval()... }) - { - return TupleToConstructor(m_byteView.Read(), std::make_index_sequence{}); - } - - - /// 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 - auto Create(T, Ts...) -> decltype(typename SplitMemberPointer::declaringType{ std::declval::type>(), std::declval::type>()... }) - { - return TupleToConstructor::declaringType>(m_byteView.Read::type, typename SplitMemberPointer::type...>(), std::make_index_sequence<1 + sizeof...(Ts)>{}); - } }; namespace Utils diff --git a/Src/Common/FSecure/CppTools/ByteConverter/Utils.h b/Src/Common/FSecure/CppTools/ByteConverter/Utils.h index 62a77fa..baa003a 100644 --- a/Src/Common/FSecure/CppTools/ByteConverter/Utils.h +++ b/Src/Common/FSecure/CppTools/ByteConverter/Utils.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifndef OBF # define OBF(x) x @@ -229,4 +230,110 @@ namespace FSecure::Utils enum { value = (sizeof(test(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 + struct WorthAddingConstRefImpl : std::false_type + {}; + + /// @brief Specialization of class checking if it is worth to take reference or copy by value. + /// Represents true. + template + struct WorthAddingConstRefImpl && (!std::is_trivial_v> || (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 + struct AddConstRefToNonTrivialImpl + { + using type = std::remove_reference_t; + }; + + /// @brief Specialization of class declaring simpler type to use based on T type. + /// Represents copy by reference. + template + struct AddConstRefToNonTrivialImpl::value>> + { + using type = std::remove_reference_t const&; + }; + } + + /// @brief Class checking if it is worth to take reference or copy by value. + template + struct WorthAddingConstRef : Detail::WorthAddingConstRefImpl {}; + + /// @brief Simplified WorthAddingConstRef::value. + template + static constexpr auto WorthAddingConstRefV = WorthAddingConstRef::value; + + /// @brief Class declaring simpler type to use based on T type. + template + struct AddConstRefToNonTrivial : Detail::AddConstRefToNonTrivialImpl {}; + + /// @brief Simplified AddConstRefToNonTrivial::type. + template + using AddConstRefToNonTrivialT = typename AddConstRefToNonTrivial::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 + auto MakeConversionTuple(Args&& ...args) + { + return std::tuple...>(std::forward(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 + struct Braces + { + template + constexpr auto operator () (As&& ... as) const + { + return T{ std::forward(as)... }; + } + }; + + /// @brief Type used for construction with parentheses. + template + struct Parentheses + { + template + constexpr auto operator () (As&& ... as) const + { + return T(std::forward(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 constexpr auto Apply(). Tuple types will be passed by parameter pack. + /// @tparam Tpl Tuple with types on which logic will be applied. + template + struct Apply + { + private: + template + constexpr static auto ApplyImpl(std::index_sequence) + { + return T::template Apply...>(); + } + public: + constexpr static auto value = ApplyImpl(std::make_index_sequence::value>{}); + }; } diff --git a/Src/Common/FSecure/WinTools/HostInfo.h b/Src/Common/FSecure/WinTools/HostInfo.h index 8a2a3e1..887ec60 100644 --- a/Src/Common/FSecure/WinTools/HostInfo.h +++ b/Src/Common/FSecure/WinTools/HostInfo.h @@ -36,63 +36,35 @@ namespace FSecure /// overload ByteConverter for RTL_OSVERSIONINFOEXW. szCSDVersion and wSuiteMask are omitted. template<> - struct ByteConverter + struct ByteConverter : PointerTupleConverter { - /// 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 + struct ByteConverter : TupleConverter { - /// 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); } }; } diff --git a/Src/Core/RouteId.h b/Src/Core/RouteId.h index 69c6d3a..1e28095 100644 --- a/Src/Core/RouteId.h +++ b/Src/Core/RouteId.h @@ -78,21 +78,11 @@ namespace FSecure { /// Specialize ByteConverter for RouteId. template <> - struct ByteConverter + struct ByteConverter : TupleConverter { - 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().GetAgentId()), decltype(std::declval().GetInterfaceId())>(); + return Utils::MakeConversionTuple(obj.GetAgentId(), obj.GetInterfaceId()); } };