commit dcab16178d819d3f5db00a44ceada078ae5cada6 Author: Bartosz Wieczorek Date: Wed Jul 5 11:40:54 2023 +0200 init diff --git a/.clang-format b/.clang-format new file mode 100755 index 0000000..81889d8 --- /dev/null +++ b/.clang-format @@ -0,0 +1,68 @@ +AccessModifierOffset: -2 +AlignAfterOpenBracket: false +AlignConsecutiveAssignments: true +# AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: false +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +# AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +# BreakStringLiterals: false +ColumnLimit: 140 +# CommentPragmas: +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 1 +ContinuationIndentWidth: 2 +Cpp11BracedListStyle: true +DerivePointerAlignment : false +DisableFormat: false +# ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +# IncludeCategories: +# IncludeIsMainRegex: +IndentCaseLabels: true +IndentWidth: 4 +# IndentWrappedFunctionNames: +KeepEmptyLinesAtTheStartOfBlocks: false +Language: Cpp +# MacroBlockBegin: +# MacroBlockEnd: +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: Inner +# PenaltyBreakBeforeFirstCallParameter: +# PenaltyBreakComment: +# PenaltyBreakFirstLessLess: +# PenaltyBreakString: +# PenaltyExcessCharacter: +# PenaltyReturnTypeOnItsOwnLine: +PointerAlignment: Middle +# ReflowComments: false +SortIncludes: true +SpaceAfterCStyleCast: true +# SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Never +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: true +SpacesInCStyleCastParentheses: true +SpacesInContainerLiterals: true +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4a0b530 --- /dev/null +++ b/.gitignore @@ -0,0 +1,74 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e3131ca --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.5) + +project(rublon-ssh LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_compile_options(-Wall -Wextra -Wpedantic) + +add_subdirectory(PAM/ssh) diff --git a/PAM/ssh/CMakeLists.txt b/PAM/ssh/CMakeLists.txt new file mode 100644 index 0000000..714c814 --- /dev/null +++ b/PAM/ssh/CMakeLists.txt @@ -0,0 +1,9 @@ +add_subdirectory(extern/rapidjson) + +include_directories( + extern/rapidjson/include +) + +add_subdirectory(src) + +add_subdirectory(tests) diff --git a/PAM/ssh/extern/rapidjson b/PAM/ssh/extern/rapidjson new file mode 160000 index 0000000..973dc9c --- /dev/null +++ b/PAM/ssh/extern/rapidjson @@ -0,0 +1 @@ +Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 diff --git a/PAM/ssh/extern/tl/expected.hpp b/PAM/ssh/extern/tl/expected.hpp new file mode 100644 index 0000000..afee404 --- /dev/null +++ b/PAM/ssh/extern/tl/expected.hpp @@ -0,0 +1,2444 @@ +/// +// expected - An implementation of std::expected with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at http://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_EXPECTED_HPP +#define TL_EXPECTED_HPP + +#define TL_EXPECTED_VERSION_MAJOR 1 +#define TL_EXPECTED_VERSION_MINOR 1 +#define TL_EXPECTED_VERSION_PATCH 0 + +#include +#include +#include +#include + +#if defined(__EXCEPTIONS) || defined(_CPPUNWIND) +#define TL_EXPECTED_EXCEPTIONS_ENABLED +#endif + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_EXPECTED_MSVC2015 +#define TL_EXPECTED_MSVC2015_CONSTEXPR +#else +#define TL_EXPECTED_MSVC2015_CONSTEXPR constexpr +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_EXPECTED_GCC55 +#endif + +#if !defined(TL_ASSERT) +//can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug +#if (__cplusplus > 201103L) && !defined(TL_EXPECTED_GCC49) +#include +#define TL_ASSERT(x) assert(x) +#else +#define TL_ASSERT(x) +#endif +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions + +#define TL_EXPECTED_NO_CONSTRR +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::has_trivial_copy_assign + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks +// std::vector for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { +namespace detail { +template +struct is_trivially_copy_constructible + : std::is_trivially_copy_constructible {}; +#ifdef _GLIBCXX_VECTOR +template +struct is_trivially_copy_constructible> : std::false_type {}; +#endif +} // namespace detail +} // namespace tl +#endif + +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#else +#define TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible +#define TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable +#define TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(T) \ + std::is_trivially_destructible +#endif + +#if __cplusplus > 201103L +#define TL_EXPECTED_CXX14 +#endif + +#ifdef TL_EXPECTED_GCC49 +#define TL_EXPECTED_GCC49_CONSTEXPR +#else +#define TL_EXPECTED_GCC49_CONSTEXPR constexpr +#endif + +#if (__cplusplus == 201103L || defined(TL_EXPECTED_MSVC2015) || \ + defined(TL_EXPECTED_GCC49)) +#define TL_EXPECTED_11_CONSTEXPR +#else +#define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +namespace tl { +template class expected; + +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX +class monostate {}; + +struct in_place_t { + explicit in_place_t() = default; +}; +static constexpr in_place_t in_place{}; +#endif + +template class unexpected { +public: + static_assert(!std::is_same::value, "E must not be void"); + + unexpected() = delete; + constexpr explicit unexpected(const E &e) : m_val(e) {} + + constexpr explicit unexpected(E &&e) : m_val(std::move(e)) {} + + template ::value>::type * = nullptr> + constexpr explicit unexpected(Args &&...args) + : m_val(std::forward(args)...) {} + template < + class U, class... Args, + typename std::enable_if &, Args &&...>::value>::type * = nullptr> + constexpr explicit unexpected(std::initializer_list l, Args &&...args) + : m_val(l, std::forward(args)...) {} + + constexpr const E &value() const & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &value() & { return m_val; } + TL_EXPECTED_11_CONSTEXPR E &&value() && { return std::move(m_val); } + constexpr const E &&value() const && { return std::move(m_val); } + +private: + E m_val; +}; + +#ifdef __cpp_deduction_guides +template unexpected(E) -> unexpected; +#endif + +template +constexpr bool operator==(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() == rhs.value(); +} +template +constexpr bool operator!=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() != rhs.value(); +} +template +constexpr bool operator<(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() < rhs.value(); +} +template +constexpr bool operator<=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() <= rhs.value(); +} +template +constexpr bool operator>(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() > rhs.value(); +} +template +constexpr bool operator>=(const unexpected &lhs, const unexpected &rhs) { + return lhs.value() >= rhs.value(); +} + +template +unexpected::type> make_unexpected(E &&e) { + return unexpected::type>(std::forward(e)); +} + +struct unexpect_t { + unexpect_t() = default; +}; +static constexpr unexpect_t unexpect{}; + +namespace detail { +template +[[noreturn]] TL_EXPECTED_11_CONSTEXPR void throw_exception(E &&e) { +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + throw std::forward(e); +#else + (void)e; +#ifdef _MSC_VER + __assume(0); +#else + __builtin_unreachable(); +#endif +#endif +} + +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX +// C++14-style aliases for brevity +template using remove_const_t = typename std::remove_const::type; +template +using remove_reference_t = typename std::remove_reference::type; +template using decay_t = typename std::decay::type; +template +using enable_if_t = typename std::enable_if::type; +template +using conditional_t = typename std::conditional::type; + +// std::conjunction from C++17 +template struct conjunction : std::true_type {}; +template struct conjunction : B {}; +template +struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + +// In C++11 mode, there's an issue in libc++'s std::mem_fn +// which results in a hard-error when using it in a noexcept expression +// in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +template +struct is_pointer_to_non_const_member_func : std::false_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; +template +struct is_pointer_to_non_const_member_func + : std::true_type {}; + +template struct is_const_or_const_ref : std::false_type {}; +template struct is_const_or_const_ref : std::true_type {}; +template struct is_const_or_const_ref : std::true_type {}; +#endif + +// std::invoke from C++17 +// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround +template < + typename Fn, typename... Args, +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + typename = enable_if_t::value && + is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, int = 0> +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); +} + +template >::value>> +constexpr auto invoke(Fn &&f, Args &&...args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); +} + +// std::invoke_result from C++17 +template struct invoke_result_impl; + +template +struct invoke_result_impl< + F, + decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = + decltype(detail::invoke(std::declval(), std::declval()...)); +}; + +template +using invoke_result = invoke_result_impl; + +template +using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 +// TODO make a version which works with MSVC 2015 +template struct is_swappable : std::true_type {}; + +template struct is_nothrow_swappable : std::true_type {}; +#else +// https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept +namespace swap_adl_tests { +// if swap ADL finds this then it would call std::swap otherwise (same +// signature) +struct tag {}; + +template tag swap(T &, T &); +template tag swap(T (&a)[N], T (&b)[N]); + +// helper functions to test if an unqualified swap is possible, and if it +// becomes std::swap +template std::false_type can_swap(...) noexcept(false); +template (), std::declval()))> +std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + +template std::false_type uses_std(...); +template +std::is_same(), std::declval())), tag> +uses_std(int); + +template +struct is_std_swap_noexcept + : std::integral_constant::value && + std::is_nothrow_move_assignable::value> {}; + +template +struct is_std_swap_noexcept : is_std_swap_noexcept {}; + +template +struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; +} // namespace swap_adl_tests + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + +template +struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std( + 0))::value || + is_swappable::value)> {}; + +template +struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> {}; +#endif +#endif + +// Trait for checking if a type is a tl::expected +template struct is_expected_impl : std::false_type {}; +template +struct is_expected_impl> : std::true_type {}; +template using is_expected = is_expected_impl>; + +template +using expected_enable_forward_value = detail::enable_if_t< + std::is_constructible::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value && + !std::is_same, detail::decay_t>::value>; + +template +using expected_enable_from_other = detail::enable_if_t< + std::is_constructible::value && + std::is_constructible::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_constructible &>::value && + !std::is_constructible &&>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value && + !std::is_convertible &, T>::value && + !std::is_convertible &&, T>::value>; + +template +using is_void_or = conditional_t::value, std::true_type, U>; + +template +using is_copy_constructible_or_void = + is_void_or>; + +template +using is_move_constructible_or_void = + is_void_or>; + +template +using is_copy_assignable_or_void = is_void_or>; + +template +using is_move_assignable_or_void = is_void_or>; + +} // namespace detail + +namespace detail { +struct no_init_t {}; +static constexpr no_init_t no_init{}; + +// Implements the storage of the values, and ensures that the destructor is +// trivial if it can be. +// +// This specialization is for where neither `T` or `E` is trivially +// destructible, so the destructors must be called on destruction of the +// `expected` +template ::value, + bool = std::is_trivially_destructible::value> +struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } else { + m_unexpect.~unexpected(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// This specialization is for when both `T` and `E` are trivially-destructible, +// so the destructor of the `expected` can be trivial. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// T is trivial, E is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + TL_EXPECTED_MSVC2015_CONSTEXPR expected_storage_base(no_init_t) + : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// E is trivial, T is not. +template struct expected_storage_base { + constexpr expected_storage_base() : m_val(T{}), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_no_init(), m_has_val(false) {} + + template ::value> * = + nullptr> + constexpr expected_storage_base(in_place_t, Args &&...args) + : m_val(std::forward(args)...), m_has_val(true) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected_storage_base(in_place_t, std::initializer_list il, + Args &&...args) + : m_val(il, std::forward(args)...), m_has_val(true) {} + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (m_has_val) { + m_val.~T(); + } + } + union { + T m_val; + unexpected m_unexpect; + char m_no_init; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is trivially-destructible +template struct expected_storage_base { + #if __GNUC__ <= 5 + //no constexpr for GCC 4/5 bug + #else + TL_EXPECTED_MSVC2015_CONSTEXPR + #endif + expected_storage_base() : m_has_val(true) {} + + constexpr expected_storage_base(no_init_t) : m_val(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() = default; + struct dummy {}; + union { + unexpected m_unexpect; + dummy m_val; + }; + bool m_has_val; +}; + +// `T` is `void`, `E` is not trivially-destructible +template struct expected_storage_base { + constexpr expected_storage_base() : m_dummy(), m_has_val(true) {} + constexpr expected_storage_base(no_init_t) : m_dummy(), m_has_val(false) {} + + constexpr expected_storage_base(in_place_t) : m_dummy(), m_has_val(true) {} + + template ::value> * = + nullptr> + constexpr explicit expected_storage_base(unexpect_t, Args &&...args) + : m_unexpect(std::forward(args)...), m_has_val(false) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected_storage_base(unexpect_t, + std::initializer_list il, + Args &&...args) + : m_unexpect(il, std::forward(args)...), m_has_val(false) {} + + ~expected_storage_base() { + if (!m_has_val) { + m_unexpect.~unexpected(); + } + } + + union { + unexpected m_unexpect; + char m_dummy; + }; + bool m_has_val; +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct(Args &&...args) noexcept { + new (std::addressof(this->m_val)) T(std::forward(args)...); + this->m_has_val = true; + } + + template void construct_with(Rhs &&rhs) noexcept { + new (std::addressof(this->m_val)) T(std::forward(rhs).get()); + this->m_has_val = true; + } + + template void construct_error(Args &&...args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + + // These assign overloads ensure that the most efficient assignment + // implementation is used while maintaining the strong exception guarantee. + // The problematic case is where rhs has a value, but *this does not. + // + // This overload handles the case where we can just copy-construct `T` + // directly into place without throwing. + template ::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + // This overload handles the case where we can attempt to create a copy of + // `T`, then no-throw move it into place if the copy was successful. + template ::value && + std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + T tmp = rhs.get(); + geterr().~unexpected(); + construct(std::move(tmp)); + } else { + assign_common(rhs); + } + } + + // This overload is the worst-case, where we have to move-construct the + // unexpected value into temporary storage, then try to copy the T into place. + // If the construction succeeds, then everything is fine, but if it throws, + // then we move the old unexpected value back into place before rethrowing the + // exception. + template ::value && + !std::is_nothrow_move_constructible::value> + * = nullptr> + void assign(const expected_operations_base &rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(rhs.get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(rhs.get()); +#endif + } else { + assign_common(rhs); + } + } + + // These overloads do the same as above, but for rvalues + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + + template ::value> + * = nullptr> + void assign(expected_operations_base &&rhs) { + if (!this->m_has_val && rhs.m_has_val) { + auto tmp = std::move(geterr()); + geterr().~unexpected(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + construct(std::move(rhs).get()); + } catch (...) { + geterr() = std::move(tmp); + throw; + } +#else + construct(std::move(rhs).get()); +#endif + } else { + assign_common(std::move(rhs)); + } + } + +#else + + // If exceptions are disabled then we can just copy-construct + void assign(const expected_operations_base &rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(rhs.get()); + } else { + assign_common(rhs); + } + } + + void assign(expected_operations_base &&rhs) noexcept { + if (!this->m_has_val && rhs.m_has_val) { + geterr().~unexpected(); + construct(std::move(rhs).get()); + } else { + assign_common(std::move(rhs)); + } + } + +#endif + + // The common part of move/copy assigning + template void assign_common(Rhs &&rhs) { + if (this->m_has_val) { + if (rhs.m_has_val) { + get() = std::forward(rhs).get(); + } else { + destroy_val(); + construct_error(std::forward(rhs).geterr()); + } + } else { + if (!rhs.m_has_val) { + geterr() = std::forward(rhs).geterr(); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR T &get() & { return this->m_val; } + constexpr const T &get() const & { return this->m_val; } + TL_EXPECTED_11_CONSTEXPR T &&get() && { return std::move(this->m_val); } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const T &&get() const && { return std::move(this->m_val); } +#endif + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { get().~T(); } +}; + +// This base class provides some handy member functions which can be used in +// further derived classes +template +struct expected_operations_base : expected_storage_base { + using expected_storage_base::expected_storage_base; + + template void construct() noexcept { this->m_has_val = true; } + + // This function doesn't use its argument, but needs it so that code in + // levels above this can work independently of whether T is void + template void construct_with(Rhs &&) noexcept { + this->m_has_val = true; + } + + template void construct_error(Args &&...args) noexcept { + new (std::addressof(this->m_unexpect)) + unexpected(std::forward(args)...); + this->m_has_val = false; + } + + template void assign(Rhs &&rhs) noexcept { + if (!this->m_has_val) { + if (rhs.m_has_val) { + geterr().~unexpected(); + construct(); + } else { + geterr() = std::forward(rhs).geterr(); + } + } else { + if (!rhs.m_has_val) { + construct_error(std::forward(rhs).geterr()); + } + } + } + + bool has_value() const { return this->m_has_val; } + + TL_EXPECTED_11_CONSTEXPR unexpected &geterr() & { + return this->m_unexpect; + } + constexpr const unexpected &geterr() const & { return this->m_unexpect; } + TL_EXPECTED_11_CONSTEXPR unexpected &&geterr() && { + return std::move(this->m_unexpect); + } +#ifndef TL_EXPECTED_NO_CONSTRR + constexpr const unexpected &&geterr() const && { + return std::move(this->m_unexpect); + } +#endif + + TL_EXPECTED_11_CONSTEXPR void destroy_val() { + // no-op + } +}; + +// This class manages conditionally having a trivial copy constructor +// This specialization is for when T and E are trivially copy constructible +template :: + value &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value> +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are not trivially copy constructible +template +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; + + expected_copy_base() = default; + expected_copy_base(const expected_copy_base &rhs) + : expected_operations_base(no_init) { + if (rhs.has_value()) { + this->construct_with(rhs); + } else { + this->construct_error(rhs.geterr()); + } + } + + expected_copy_base(expected_copy_base &&rhs) = default; + expected_copy_base &operator=(const expected_copy_base &rhs) = default; + expected_copy_base &operator=(expected_copy_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move constructor +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_constructible. We +// have to make do with a non-trivial move constructor even if T is trivially +// move constructible +#ifndef TL_EXPECTED_GCC49 +template >::value + &&std::is_trivially_move_constructible::value> +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; +}; +#else +template struct expected_move_base; +#endif +template +struct expected_move_base : expected_copy_base { + using expected_copy_base::expected_copy_base; + + expected_move_base() = default; + expected_move_base(const expected_move_base &rhs) = default; + + expected_move_base(expected_move_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value) + : expected_copy_base(no_init) { + if (rhs.has_value()) { + this->construct_with(std::move(rhs)); + } else { + this->construct_error(std::move(rhs.geterr())); + } + } + expected_move_base &operator=(const expected_move_base &rhs) = default; + expected_move_base &operator=(expected_move_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial copy assignment operator +template >::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_ASSIGNABLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E)::value + &&TL_EXPECTED_IS_TRIVIALLY_DESTRUCTIBLE(E)::value> +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; +}; + +template +struct expected_copy_assign_base : expected_move_base { + using expected_move_base::expected_move_base; + + expected_copy_assign_base() = default; + expected_copy_assign_base(const expected_copy_assign_base &rhs) = default; + + expected_copy_assign_base(expected_copy_assign_base &&rhs) = default; + expected_copy_assign_base &operator=(const expected_copy_assign_base &rhs) { + this->assign(rhs); + return *this; + } + expected_copy_assign_base & + operator=(expected_copy_assign_base &&rhs) = default; +}; + +// This class manages conditionally having a trivial move assignment operator +// Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it +// doesn't implement an analogue to std::is_trivially_move_assignable. We have +// to make do with a non-trivial move assignment operator even if T is trivially +// move assignable +#ifndef TL_EXPECTED_GCC49 +template , + std::is_trivially_move_constructible, + std::is_trivially_move_assignable>>:: + value &&std::is_trivially_destructible::value + &&std::is_trivially_move_constructible::value + &&std::is_trivially_move_assignable::value> +struct expected_move_assign_base : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; +}; +#else +template struct expected_move_assign_base; +#endif + +template +struct expected_move_assign_base + : expected_copy_assign_base { + using expected_copy_assign_base::expected_copy_assign_base; + + expected_move_assign_base() = default; + expected_move_assign_base(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base(expected_move_assign_base &&rhs) = default; + + expected_move_assign_base & + operator=(const expected_move_assign_base &rhs) = default; + + expected_move_assign_base & + operator=(expected_move_assign_base &&rhs) noexcept( + std::is_nothrow_move_constructible::value + &&std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } +}; + +// expected_delete_ctor_base will conditionally delete copy and move +// constructors depending on whether T is copy/move constructible +template ::value && + std::is_copy_constructible::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value)> +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = default; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +template +struct expected_delete_ctor_base { + expected_delete_ctor_base() = default; + expected_delete_ctor_base(const expected_delete_ctor_base &) = delete; + expected_delete_ctor_base(expected_delete_ctor_base &&) noexcept = delete; + expected_delete_ctor_base & + operator=(const expected_delete_ctor_base &) = default; + expected_delete_ctor_base & + operator=(expected_delete_ctor_base &&) noexcept = default; +}; + +// expected_delete_assign_base will conditionally delete copy and move +// constructors depending on whether T and E are copy/move constructible + +// assignable +template ::value && + std::is_copy_constructible::value && + is_copy_assignable_or_void::value && + std::is_copy_assignable::value), + bool EnableMove = (is_move_constructible_or_void::value && + std::is_move_constructible::value && + is_move_assignable_or_void::value && + std::is_move_assignable::value)> +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = default; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = default; +}; + +template +struct expected_delete_assign_base { + expected_delete_assign_base() = default; + expected_delete_assign_base(const expected_delete_assign_base &) = default; + expected_delete_assign_base(expected_delete_assign_base &&) noexcept = + default; + expected_delete_assign_base & + operator=(const expected_delete_assign_base &) = delete; + expected_delete_assign_base & + operator=(expected_delete_assign_base &&) noexcept = delete; +}; + +// This is needed to be able to construct the expected_default_ctor_base which +// follows, while still conditionally deleting the default constructor. +struct default_constructor_tag { + explicit constexpr default_constructor_tag() = default; +}; + +// expected_default_ctor_base will ensure that expected has a deleted default +// consturctor if T is not default constructible. +// This specialization is for when T is default constructible +template ::value || std::is_void::value> +struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = default; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; + +// This specialization is for when T is not default constructible +template struct expected_default_ctor_base { + constexpr expected_default_ctor_base() noexcept = delete; + constexpr expected_default_ctor_base( + expected_default_ctor_base const &) noexcept = default; + constexpr expected_default_ctor_base(expected_default_ctor_base &&) noexcept = + default; + expected_default_ctor_base & + operator=(expected_default_ctor_base const &) noexcept = default; + expected_default_ctor_base & + operator=(expected_default_ctor_base &&) noexcept = default; + + constexpr explicit expected_default_ctor_base(default_constructor_tag) {} +}; +} // namespace detail + +template class bad_expected_access : public std::exception { +public: + explicit bad_expected_access(E e) : m_val(std::move(e)) {} + + virtual const char *what() const noexcept override { + return "Bad expected access"; + } + + const E &error() const & { return m_val; } + E &error() & { return m_val; } + const E &&error() const && { return std::move(m_val); } + E &&error() && { return std::move(m_val); } + +private: + E m_val; +}; + +/// An `expected` object is an object that contains the storage for +/// another object and manages the lifetime of this contained object `T`. +/// Alternatively it could contain the storage for another unexpected object +/// `E`. The contained object may not be initialized after the expected object +/// has been initialized, and may not be destroyed before the expected object +/// has been destroyed. The initialization state of the contained object is +/// tracked by the expected object. +template +class expected : private detail::expected_move_assign_base, + private detail::expected_delete_ctor_base, + private detail::expected_delete_assign_base, + private detail::expected_default_ctor_base { + static_assert(!std::is_reference::value, "T must not be a reference"); + static_assert(!std::is_same::type>::value, + "T must not be in_place_t"); + static_assert(!std::is_same::type>::value, + "T must not be unexpect_t"); + static_assert( + !std::is_same>::type>::value, + "T must not be unexpected"); + static_assert(!std::is_reference::value, "E must not be a reference"); + + T *valptr() { return std::addressof(this->m_val); } + const T *valptr() const { return std::addressof(this->m_val); } + unexpected *errptr() { return std::addressof(this->m_unexpect); } + const unexpected *errptr() const { + return std::addressof(this->m_unexpect); + } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &val() { + return this->m_val; + } + TL_EXPECTED_11_CONSTEXPR unexpected &err() { return this->m_unexpect; } + + template ::value> * = nullptr> + constexpr const U &val() const { + return this->m_val; + } + constexpr const unexpected &err() const { return this->m_unexpect; } + + using impl_base = detail::expected_move_assign_base; + using ctor_base = detail::expected_default_ctor_base; + +public: + typedef T value_type; + typedef E error_type; + typedef unexpected unexpected_type; + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & { + return and_then_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && { + return and_then_impl(std::move(*this), std::forward(f)); + } + template constexpr auto and_then(F &&f) const & { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template constexpr auto and_then(F &&f) const && { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif + +#else + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) & -> decltype(and_then_impl(std::declval(), + std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR auto + and_then(F &&f) && -> decltype(and_then_impl(std::declval(), + std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } + template + constexpr auto and_then(F &&f) const & -> decltype(and_then_impl( + std::declval(), std::forward(f))) { + return and_then_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr auto and_then(F &&f) const && -> decltype(and_then_impl( + std::declval(), std::forward(f))) { + return and_then_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + map(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + map(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + template constexpr auto transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl( + std::declval(), std::declval())) + transform(F &&f) & { + return expected_map_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) && { + return expected_map_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const & { + return expected_map_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(expected_map_impl(std::declval(), + std::declval())) + transform(F &&f) const && { + return expected_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + map_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) + template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template TL_EXPECTED_11_CONSTEXPR auto transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template constexpr auto transform_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + template constexpr auto transform_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#else + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) & { + return map_error_impl(*this, std::forward(f)); + } + template + TL_EXPECTED_11_CONSTEXPR decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) && { + return map_error_impl(std::move(*this), std::forward(f)); + } + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const & { + return map_error_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template + constexpr decltype(map_error_impl(std::declval(), + std::declval())) + transform_error(F &&f) const && { + return map_error_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) & { + return or_else_impl(*this, std::forward(f)); + } + + template expected TL_EXPECTED_11_CONSTEXPR or_else(F &&f) && { + return or_else_impl(std::move(*this), std::forward(f)); + } + + template expected constexpr or_else(F &&f) const & { + return or_else_impl(*this, std::forward(f)); + } + +#ifndef TL_EXPECTED_NO_CONSTRR + template expected constexpr or_else(F &&f) const && { + return or_else_impl(std::move(*this), std::forward(f)); + } +#endif + constexpr expected() = default; + constexpr expected(const expected &rhs) = default; + constexpr expected(expected &&rhs) = default; + expected &operator=(const expected &rhs) = default; + expected &operator=(expected &&rhs) = default; + + template ::value> * = + nullptr> + constexpr expected(in_place_t, Args &&...args) + : impl_base(in_place, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr expected(in_place_t, std::initializer_list il, Args &&...args) + : impl_base(in_place, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr, + detail::enable_if_t::value> * = + nullptr> + explicit constexpr expected(const unexpected &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected const &e) + : impl_base(unexpect, e.value()), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + explicit constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template < + class G = E, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t::value> * = nullptr> + constexpr expected(unexpected &&e) noexcept( + std::is_nothrow_constructible::value) + : impl_base(unexpect, std::move(e.value())), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value> * = + nullptr> + constexpr explicit expected(unexpect_t, Args &&...args) + : impl_base(unexpect, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template &, Args &&...>::value> * = nullptr> + constexpr explicit expected(unexpect_t, std::initializer_list il, + Args &&...args) + : impl_base(unexpect, il, std::forward(args)...), + ctor_base(detail::default_constructor_tag{}) {} + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template ::value && + std::is_convertible::value)> * = + nullptr, + detail::expected_enable_from_other + * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(const expected &rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(*rhs); + } else { + this->construct_error(rhs.error()); + } + } + + template < + class U, class G, + detail::enable_if_t::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + explicit TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U, class G, + detail::enable_if_t<(std::is_convertible::value && + std::is_convertible::value)> * = nullptr, + detail::expected_enable_from_other * = nullptr> + TL_EXPECTED_11_CONSTEXPR expected(expected &&rhs) + : ctor_base(detail::default_constructor_tag{}) { + if (rhs.has_value()) { + this->construct(std::move(*rhs)); + } else { + this->construct_error(std::move(rhs.error())); + } + } + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + explicit TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, + detail::enable_if_t::value> * = nullptr, + detail::expected_enable_forward_value * = nullptr> + TL_EXPECTED_MSVC2015_CONSTEXPR expected(U &&v) + : expected(in_place, std::forward(v)) {} + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + err().~unexpected(); + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } + + return *this; + } + + template < + class U = T, class G = T, + detail::enable_if_t::value> * = + nullptr, + detail::enable_if_t::value> * = nullptr, + detail::enable_if_t< + (!std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value && + std::is_constructible::value && + std::is_assignable::value && + std::is_nothrow_move_constructible::value)> * = nullptr> + expected &operator=(U &&v) { + if (has_value()) { + val() = std::forward(v); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(v)); + this->m_has_val = true; +#endif + } + + return *this; + } + + template ::value && + std::is_assignable::value> * = nullptr> + expected &operator=(const unexpected &rhs) { + if (!has_value()) { + err() = rhs; + } else { + this->destroy_val(); + ::new (errptr()) unexpected(rhs); + this->m_has_val = false; + } + + return *this; + } + + template ::value && + std::is_move_assignable::value> * = nullptr> + expected &operator=(unexpected &&rhs) noexcept { + if (!has_value()) { + err() = std::move(rhs); + } else { + this->destroy_val(); + ::new (errptr()) unexpected(std::move(rhs)); + this->m_has_val = false; + } + + return *this; + } + + template ::value> * = nullptr> + void emplace(Args &&...args) { + if (has_value()) { + val().~T(); + } else { + err().~unexpected(); + this->m_has_val = true; + } + ::new (valptr()) T(std::forward(args)...); + } + + template ::value> * = nullptr> + void emplace(Args &&...args) { + if (has_value()) { + val().~T(); + ::new (valptr()) T(std::forward(args)...); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(std::forward(args)...); + this->m_has_val = true; +#endif + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + err().~unexpected(); + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } + } + + template &, Args &&...>::value> * = nullptr> + void emplace(std::initializer_list il, Args &&...args) { + if (has_value()) { + T t(il, std::forward(args)...); + val() = std::move(t); + } else { + auto tmp = std::move(err()); + err().~unexpected(); + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; + } catch (...) { + err() = std::move(tmp); + throw; + } +#else + ::new (valptr()) T(il, std::forward(args)...); + this->m_has_val = true; +#endif + } + } + +private: + using t_is_void = std::true_type; + using t_is_not_void = std::false_type; + using t_is_nothrow_move_constructible = std::true_type; + using move_constructing_t_can_throw = std::false_type; + using e_is_nothrow_move_constructible = std::true_type; + using move_constructing_e_can_throw = std::false_type; + + void swap_where_both_have_value(expected & /*rhs*/, t_is_void) noexcept { + // swapping void is a no-op + } + + void swap_where_both_have_value(expected &rhs, t_is_not_void) { + using std::swap; + swap(val(), rhs.val()); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_void) noexcept( + std::is_nothrow_move_constructible::value) { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value(expected &rhs, t_is_not_void) { + swap_where_only_one_has_value_and_t_is_not_void( + rhs, typename std::is_nothrow_move_constructible::type{}, + typename std::is_nothrow_move_constructible::type{}); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + e_is_nothrow_move_constructible) noexcept { + auto temp = std::move(val()); + val().~T(); + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, t_is_nothrow_move_constructible, + move_constructing_e_can_throw) { + auto temp = std::move(val()); + val().~T(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + val() = std::move(temp); + throw; + } +#else + ::new (errptr()) unexpected_type(std::move(rhs.err())); + rhs.err().~unexpected_type(); + ::new (rhs.valptr()) T(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + + void swap_where_only_one_has_value_and_t_is_not_void( + expected &rhs, move_constructing_t_can_throw, + e_is_nothrow_move_constructible) { + auto temp = std::move(rhs.err()); + rhs.err().~unexpected_type(); +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED + try { + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); + } catch (...) { + rhs.err() = std::move(temp); + throw; + } +#else + ::new (rhs.valptr()) T(std::move(val())); + val().~T(); + ::new (errptr()) unexpected_type(std::move(temp)); + std::swap(this->m_has_val, rhs.m_has_val); +#endif + } + +public: + template + detail::enable_if_t::value && + detail::is_swappable::value && + (std::is_nothrow_move_constructible::value || + std::is_nothrow_move_constructible::value)> + swap(expected &rhs) noexcept( + std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value + &&std::is_nothrow_move_constructible::value + &&detail::is_nothrow_swappable::value) { + if (has_value() && rhs.has_value()) { + swap_where_both_have_value(rhs, typename std::is_void::type{}); + } else if (!has_value() && rhs.has_value()) { + rhs.swap(*this); + } else if (has_value()) { + swap_where_only_one_has_value(rhs, typename std::is_void::type{}); + } else { + using std::swap; + swap(err(), rhs.err()); + } + } + + constexpr const T *operator->() const { + TL_ASSERT(has_value()); + return valptr(); + } + TL_EXPECTED_11_CONSTEXPR T *operator->() { + TL_ASSERT(has_value()); + return valptr(); + } + + template ::value> * = nullptr> + constexpr const U &operator*() const & { + TL_ASSERT(has_value()); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &operator*() & { + TL_ASSERT(has_value()); + return val(); + } + template ::value> * = nullptr> + constexpr const U &&operator*() const && { + TL_ASSERT(has_value()); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&operator*() && { + TL_ASSERT(has_value()); + return std::move(val()); + } + + constexpr bool has_value() const noexcept { return this->m_has_val; } + constexpr explicit operator bool() const noexcept { return this->m_has_val; } + + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &value() const & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + detail::throw_exception(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &&value() && { + if (!has_value()) + detail::throw_exception(bad_expected_access(std::move(err()).value())); + return std::move(val()); + } + + constexpr const E &error() const & { + TL_ASSERT(!has_value()); + return err().value(); + } + TL_EXPECTED_11_CONSTEXPR E &error() & { + TL_ASSERT(!has_value()); + return err().value(); + } + constexpr const E &&error() const && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + TL_EXPECTED_11_CONSTEXPR E &&error() && { + TL_ASSERT(!has_value()); + return std::move(err().value()); + } + + template constexpr T value_or(U &&v) const & { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy-constructible and convertible to from U&&"); + return bool(*this) ? **this : static_cast(std::forward(v)); + } + template TL_EXPECTED_11_CONSTEXPR T value_or(U &&v) && { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move-constructible and convertible to from U&&"); + return bool(*this) ? std::move(**this) : static_cast(std::forward(v)); + } +}; + +namespace detail { +template using exp_t = typename detail::decay_t::value_type; +template using err_t = typename detail::decay_t::error_type; +template using ret_t = expected>; + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval()))> +constexpr auto and_then_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#else +template struct TC; +template (), + *std::declval())), + detail::enable_if_t>::value> * = nullptr> +auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() + ? detail::invoke(std::forward(f), *std::forward(exp)) + : Ret(unexpect, std::forward(exp).error()); +} + +template ())), + detail::enable_if_t>::value> * = nullptr> +constexpr auto and_then_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + + return exp.has_value() ? detail::invoke(std::forward(f)) + : Ret(unexpect, std::forward(exp).error()); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +constexpr auto expected_map_impl(Exp &&exp, F &&f) { + using result = ret_t>; + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> +auto expected_map_impl(Exp &&exp, F &&f) { + using result = expected>; + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return result(); + } + + return result(unexpect, std::forward(exp).error()); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f), + *std::forward(exp))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + *std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f), *std::forward(exp)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +constexpr auto expected_map_impl(Exp &&exp, F &&f) + -> ret_t> { + using result = ret_t>; + + return exp.has_value() ? result(detail::invoke(std::forward(f))) + : result(unexpect, std::forward(exp).error()); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval())), + detail::enable_if_t::value> * = nullptr> + +auto expected_map_impl(Exp &&exp, F &&f) -> expected> { + if (exp.has_value()) { + detail::invoke(std::forward(f)); + return {}; + } + + return unexpected>(std::forward(exp).error()); +} +#endif + +#if defined(TL_EXPECTED_CXX14) && !defined(TL_EXPECTED_GCC49) && \ + !defined(TL_EXPECTED_GCC54) && !defined(TL_EXPECTED_GCC55) +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, detail::decay_t>; + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#else +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result(*std::forward(exp)) + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(*std::forward(exp)); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto map_error_impl(Exp &&exp, F &&f) + -> expected, detail::decay_t> { + using result = expected, detail::decay_t>; + + return exp.has_value() + ? result() + : result(unexpect, detail::invoke(std::forward(f), + std::forward(exp).error())); +} + +template >::value> * = nullptr, + class Ret = decltype(detail::invoke(std::declval(), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto map_error_impl(Exp &&exp, F &&f) -> expected, monostate> { + using result = expected, monostate>; + if (exp.has_value()) { + return result(); + } + + detail::invoke(std::forward(f), std::forward(exp).error()); + return result(unexpect, monostate{}); +} +#endif + +#ifdef TL_EXPECTED_CXX14 +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +constexpr auto or_else_impl(Exp &&exp, F &&f) { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), + std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() ? std::forward(exp) + : (detail::invoke(std::forward(f), + std::forward(exp).error()), + std::forward(exp)); +} +#else +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +auto or_else_impl(Exp &&exp, F &&f) -> Ret { + static_assert(detail::is_expected::value, "F must return an expected"); + return exp.has_value() ? std::forward(exp) + : detail::invoke(std::forward(f), + std::forward(exp).error()); +} + +template (), + std::declval().error())), + detail::enable_if_t::value> * = nullptr> +detail::decay_t or_else_impl(Exp &&exp, F &&f) { + return exp.has_value() ? std::forward(exp) + : (detail::invoke(std::forward(f), + std::forward(exp).error()), + std::forward(exp)); +} +#endif +} // namespace detail + +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : *lhs == *rhs); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() != rhs.error() : *lhs != *rhs); +} +template +constexpr bool operator==(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? false + : (!lhs.has_value() ? lhs.error() == rhs.error() : true); +} +template +constexpr bool operator!=(const expected &lhs, + const expected &rhs) { + return (lhs.has_value() != rhs.has_value()) + ? true + : (!lhs.has_value() ? lhs.error() == rhs.error() : false); +} + +template +constexpr bool operator==(const expected &x, const U &v) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator==(const U &v, const expected &x) { + return x.has_value() ? *x == v : false; +} +template +constexpr bool operator!=(const expected &x, const U &v) { + return x.has_value() ? *x != v : true; +} +template +constexpr bool operator!=(const U &v, const expected &x) { + return x.has_value() ? *x != v : true; +} + +template +constexpr bool operator==(const expected &x, const unexpected &e) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator==(const unexpected &e, const expected &x) { + return x.has_value() ? false : x.error() == e.value(); +} +template +constexpr bool operator!=(const expected &x, const unexpected &e) { + return x.has_value() ? true : x.error() != e.value(); +} +template +constexpr bool operator!=(const unexpected &e, const expected &x) { + return x.has_value() ? true : x.error() != e.value(); +} + +template ::value || + std::is_move_constructible::value) && + detail::is_swappable::value && + std::is_move_constructible::value && + detail::is_swappable::value> * = nullptr> +void swap(expected &lhs, + expected &rhs) noexcept(noexcept(lhs.swap(rhs))) { + lhs.swap(rhs); +} +} // namespace tl + +#endif diff --git a/PAM/ssh/src/CMakeLists.txt b/PAM/ssh/src/CMakeLists.txt new file mode 100644 index 0000000..c1ffc96 --- /dev/null +++ b/PAM/ssh/src/CMakeLists.txt @@ -0,0 +1,7 @@ +set(CMAKE_SHARED_LINKER_FLAGS "-fpic -static-libstdc++ -fvisibility=hidden -ffunction-sections -fdata-sections -fwhole-program ") + +add_library( + rublon-ssh SHARED pam.cpp pam.hpp rublon.hpp curl.hpp span.hpp sign.hpp CoreHandler.hpp configuration.hpp +) + +target_link_libraries(rublon-ssh -lcurl -lssl -lcrypto) diff --git a/PAM/ssh/src/CoreHandler.hpp b/PAM/ssh/src/CoreHandler.hpp new file mode 100644 index 0000000..fded6f5 --- /dev/null +++ b/PAM/ssh/src/CoreHandler.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +#include "configuration.hpp" +#include "curl.hpp" +#include "json.hpp" +#include "sign.hpp" + +#include "../extern/tl/expected.hpp" + +namespace rublon { + +class CoreHandlerError { + public: + enum ErrorClass { BadSigature, CoreException, ConnectionError, BrokenData }; + + CoreHandlerError(ErrorClass e) : errorClass{e} {} + CoreHandlerError(ErrorClass e, std::string r) : errorClass{e}, reson{std::move(r)} {} + + ErrorClass errorClass; + std::string reson; +}; + +template < typename Impl > +class CoreHandlerInterface { + public: + tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const { + return static_cast< const Impl * >(this)->request(path, body); + } +}; + +template < typename HttpHandler = CURL > +class CoreHandler : public CoreHandlerInterface< CoreHandler< HttpHandler > > { + std::string secretKey; + std::string url; + + std::pmr::string xRublonSignature(std::pmr::memory_resource * mr, std::string_view body) const { + return signData(body, secretKey.c_str()).data(); + } + + void signRequest(std::pmr::monotonic_buffer_resource & mr, Request & request) const { + request.headers["X-Rublon-Signature"] = xRublonSignature(&mr, request.body); + } + + bool responseSigned(const Response & response) const { + auto xRubResp = response.headers.at("x-rublon-signature"); + auto sign = signData(response.body, secretKey); + return xRubResp == sign.data(); + } + + protected: + HttpHandler http{}; + + public: + void debugLog(const char *, const char *) {} + + CoreHandler(const rublon::Configuration & config) : secretKey{config.parameters.secretKey}, url{config.parameters.apiServer} {} + + tl::expected< rublon::Document, CoreHandlerError > request(std::string_view path, const rublon::Document & body) const { + std::byte _buffer[16 * 1024]; + std::pmr::monotonic_buffer_resource mr{_buffer, sizeof(_buffer)}; + + rublon::RapidJSONPMRAlloc alloc{&mr}; + rublon::StringBuffer jsonStr{&alloc}; + rublon::Writer writer{jsonStr, &alloc}; + + body.Accept(writer); + + Request request; + request.headers["Content-Type"] = "application/json"; + request.headers["Accept"] = "application/json"; + + request.body = jsonStr.GetString(); + + signRequest(mr, request); + + std::pmr::string uri{url + path.data(), &mr}; + + auto response = http.request(uri, request); + + if(not response.has_value()) { + return tl::unexpected{CoreHandlerError::ConnectionError}; + } + if(not responseSigned(*response)) { + return tl::unexpected{CoreHandlerError::BadSigature}; + } + + rublon::Document resp{&alloc}; + resp.Parse(response->body.c_str()); + + if(resp.HasParseError() or not resp.HasMember("result")) { + return tl::unexpected{CoreHandlerError::BrokenData}; + } + if(resp["result"].HasMember("exception")) { + return tl::unexpected{CoreHandlerError{CoreHandlerError::CoreException, resp["result"]["exception"].GetString()}}; + } + + return resp; + } +}; + +} // namespace rublon diff --git a/PAM/ssh/src/configuration.hpp b/PAM/ssh/src/configuration.hpp new file mode 100644 index 0000000..6a71ec3 --- /dev/null +++ b/PAM/ssh/src/configuration.hpp @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" + +namespace rublon { +class ConfigurationFactory; +class Configuration { + public: + struct Parameters { + std::string systemToken; + std::string secretKey; + std::string apiServer; + int prompt; + bool enablePasswdEmail; + bool logging; + bool autopushPrompt; + }; + + Parameters parameters; +}; + +class ConfigurationFactory { + public: + ConfigurationFactory(){}; + + std::optional< Configuration > systemConfig() { + std::array< char, 8 * 1024 > configBuffer; + std::pmr::monotonic_buffer_resource mr{configBuffer.data(), configBuffer.size()}; + + using Params = Configuration::Parameters; + Params configValues; + + /// TODO wypadałoby zmienić ścieżkę + std::ifstream file(std::filesystem::path{"/etc/rublon.config"}); + + std::pmr::string line{&mr}; + line.reserve(100); + std::pmr::map< std::pmr::string, std::pmr::string > parameters{&mr}; + + while(std::getline(file, line)) { + std::pmr::string key{&mr}; + std::pmr::string value{&mr}; + + if(!line.length()) + continue; + + if(line[0] == '#' || line[0] == ';') + continue; + + auto posEqual = line.find('='); + key = line.substr(0, posEqual); + value = line.substr(posEqual + 1); + + parameters.emplace(std::move(key), std::move(value)); + } + + auto saveStr = [&](auto member) { // + return [member](Params * params, std::string_view value) { params->*member = value; }; + }; + + auto saveInt = [](auto member) { // + return [member](Params * params, std::string_view value) { params->*member = std::stoi(value.data()); }; + }; + + auto saveBool = [](auto member) { + return [member](Params * params, std::string_view value) { params->*member = details::to_bool(value); }; + }; + + std::tuple< const char *, std::function< void(Params *, const char *) >, const char * > checks[]{// + {"logging", saveBool(&Params::logging), "true"}, + {"systemToken", saveStr(&Params::systemToken), nullptr}, + {"secretKey", saveStr(&Params::secretKey), nullptr}, + {"rublonApiServer", saveStr(&Params::apiServer), nullptr}, + {"prompt", saveInt(&Params::prompt), "1"}, + {"enablePasswdEmail", saveBool(&Params::enablePasswdEmail), "true"}, + {"autopushPrompt", saveBool(&Params::autopushPrompt), "false"}}; + + for(const auto & [key, set_func, default_value] : checks) { + if(auto it = parameters.find(key); it != parameters.end()) { + set_func(&configValues, it->second.data()); + } else { + if(default_value) { + /// TODO exit?? + } else { // not required + set_func(&configValues, default_value); + } + } + } + + return Configuration{std::move(configValues)}; + } +}; +} // namespace rublon diff --git a/PAM/ssh/src/curl.hpp b/PAM/ssh/src/curl.hpp new file mode 100644 index 0000000..4405827 --- /dev/null +++ b/PAM/ssh/src/curl.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "span.hpp" +#include "utils.hpp" + +/// TODO rename file to rublon/Core.hpp +/// TODO Create rublon utils + +namespace rublon { +static size_t WriteMemoryCallback(void * contents, size_t size, size_t nmemb, void * userp) { + size_t realsize = size * nmemb; + reinterpret_cast< std::string * >(userp)->append(static_cast< const char * >(contents), realsize); + return realsize; +} + +struct Request{ + std::map headers; + std::string body; +}; + +struct Response{ + std::map headers; + std::string body; +}; + + +class CURL { + std::unique_ptr< ::CURL, void (*)(::CURL *) > curl; + + public: + CURL() : curl{std::unique_ptr< ::CURL, void (*)(::CURL *) >(curl_easy_init(), curl_easy_cleanup)} {} + + std::optional< Response > request(std::string_view uri, const Request &request) const { + std::string response_data; + response_data.reserve(1000); + + + Response response; + + /// TODO this can be done on stack using pmr + auto curl_headers = std::unique_ptr< curl_slist, void (*)(curl_slist *) >(nullptr, curl_slist_free_all); + std::for_each(request.headers.begin(), request.headers.end(), [&](auto header) { + curl_headers.reset(curl_slist_append(curl_headers.release(),(header.first + ": " + header.second).c_str())); + }); + + curl_easy_setopt(curl.get(), CURLOPT_VERBOSE, 1); + curl_easy_setopt(curl.get(), CURLOPT_URL, uri.data()); + curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, curl_headers.get()); + curl_easy_setopt(curl.get(), CURLOPT_POST, 1); + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDS, request.body.data()); + curl_easy_setopt(curl.get(), CURLOPT_POSTFIELDSIZE, static_cast< u_int32_t >(request.body.size())); + curl_easy_setopt(curl.get(), CURLOPT_HEADER, 1); + curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteMemoryCallback); + curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_data); + + auto res = curl_easy_perform(curl.get()); + if(res != CURLE_OK) { + // debugLog("No response from Rublon server (perform)", ""); + return std::nullopt; + } + + long size; + curl_easy_getinfo(curl.get(), CURLINFO_HEADER_SIZE, &size); + + ///TODO ogarnąć alokację pamięci + response.headers = details::headers({response_data.data(), static_cast(size)}); + response.body = response_data.substr(size); + + return response; + } +}; + +} // namespace rublon diff --git a/PAM/ssh/src/init.hpp b/PAM/ssh/src/init.hpp new file mode 100644 index 0000000..e69de29 diff --git a/PAM/ssh/src/json.hpp b/PAM/ssh/src/json.hpp new file mode 100644 index 0000000..aba4959 --- /dev/null +++ b/PAM/ssh/src/json.hpp @@ -0,0 +1,88 @@ +#pragma once +#include "rapidjson/document.h" +#include "rapidjson/writer.h" + +#include +#include +namespace rublon { + +struct RapidJSONPMRAlloc { + std::pmr::memory_resource * upstream = std::pmr::get_default_resource(); + + static constexpr bool kNeedFree = true; + static constexpr auto objectOffset = alignof(std::max_align_t); + static constexpr auto memPadding = objectOffset * 2; + + void * Malloc(size_t size) { + if(size != 0) { + const auto allocated_size = size + memPadding; + std::byte * newPtr = static_cast< std::byte * >(upstream->allocate(allocated_size)); + auto * ptrToReturn = newPtr + memPadding; + // placement new a pointer to ourselves at the first memory location + new(newPtr)(RapidJSONPMRAlloc *)(this); + // placement new the size in the second location + new(newPtr + objectOffset)(std::size_t)(size); + return ptrToReturn; + } else { + return nullptr; + } + } + + void freePtr(void * origPtr, size_t originalSize) { + if(origPtr == nullptr) { + return; + } + upstream->deallocate(static_cast< std::byte * >(origPtr) - memPadding, originalSize + memPadding); + } + + void * Realloc(void * origPtr, size_t originalSize, size_t newSize) { + if(newSize == 0) { + freePtr(origPtr, originalSize); + return nullptr; + } + + if(newSize <= originalSize) { + return origPtr; + } + + void * newPtr = Malloc(newSize); + std::memcpy(newPtr, origPtr, originalSize); + freePtr(origPtr, originalSize); + return newPtr; + } + + // and Free needs to be static, which causes this whole thing + // to fall apart. This means that we have to keep our own list of allocated memory + // with our own pointers back to ourselves and our own list of sizes + // so we can push all of this back to the upstream allocator + static void Free(void * ptr) { + if(ptr == nullptr) { + return; + } + + std::byte * startOfData = static_cast< std::byte * >(ptr) - memPadding; + + auto * ptrToAllocator = *reinterpret_cast< RapidJSONPMRAlloc ** >(startOfData); + auto origAllocatedSize = *reinterpret_cast< std::size_t * >(startOfData + objectOffset); + + ptrToAllocator->freePtr(ptr, origAllocatedSize); + } +}; + +using Document = rapidjson::GenericDocument< rapidjson::UTF8<>, RapidJSONPMRAlloc >; +using Value = rapidjson::GenericValue< rapidjson::UTF8<>, RapidJSONPMRAlloc >; +using StringBuffer = rapidjson::GenericStringBuffer< rapidjson::UTF8<>, RapidJSONPMRAlloc >; +using Writer = rapidjson::Writer< StringBuffer, rapidjson::UTF8<>, rapidjson::UTF8<>, RapidJSONPMRAlloc >; + +// using value = GenericValue< UTF8<>, RapidJSONPMRAlloc >; + +// RapidJSONPMRAlloc alloc{&mr}; +// GenericDocument< UTF8<>, RapidJSONPMRAlloc > d(&alloc); +// d.SetObject(); + +// GenericDocument< UTF8<>, RapidJSONPMRAlloc >::AllocatorType & allocator = d.GetAllocator(); + +// d.AddMember("systemToken", "DUPA", allocator); +// d.AddMember("username", "$USER", allocator); +// d.AddMember("userEmail", "$USER_EMAIL", allocator); +} // namespace rublon diff --git a/PAM/ssh/src/pam.cpp b/PAM/ssh/src/pam.cpp new file mode 100644 index 0000000..b0e6167 --- /dev/null +++ b/PAM/ssh/src/pam.cpp @@ -0,0 +1,36 @@ +#include +#include +#include +#include +#include + +#include + +#include + +#include "curl.hpp" +#include "rublon.hpp" + +using namespace std; + +PAM_EXTERN int pam_sm_setcred( pam_handle_t *pamh, int flags, int argc, const char **argv ) { + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { + auto rublonConfig = rublon::ConfigurationFactory{}.systemConfig(); + + rublon::CoreHandler CH{rublonConfig.value()}; + + rublon::Init{rublonConfig.value()}.fire(CH); + + return PAM_SUCCESS; +} + +PAM_EXTERN int pam_sm_authenticate( pam_handle_t *pamh, int flags,int argc, const char **argv ) { + return PAM_SUCCESS; +} + +int main(){ + pam_sm_acct_mgmt(nullptr, 1, 0, nullptr); +} diff --git a/PAM/ssh/src/pam.hpp b/PAM/ssh/src/pam.hpp new file mode 100644 index 0000000..e261ef0 --- /dev/null +++ b/PAM/ssh/src/pam.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "utils.hpp" + +class PamAction {}; + +class PamAccept {}; +class PamDecline {}; + +class PAMPrompt { + pam_handle_t * pamh; + + public: + template < typename... Ti > + void print(const char * fmt, Ti... ti) const noexcept { + // pam_prompt(pamh, PAM_TEXT_INFO, nullptr, fmt, std::forward(ti...)); + } + + template < typename Fun, typename... Ti > + [[nodiscard]] auto scan(Fun && f, const char * fmt, Ti... ti) const noexcept { + char * responseBuffer = nullptr; + pam_prompt(pamh, PAM_TEXT_INFO, &responseBuffer, fmt, std::forward< Ti... >(ti...)); + if(responseBuffer) { + auto ret = f(responseBuffer); + free(responseBuffer); + return std::optional{ret}; + } + return std::optional< std::result_of_t< Fun(char *) > >(); + } +}; + +class LinuxPam { + pam_handle_t * pamh; + + public: + rublon::NonOwningPtr< const char > ip() const { + const char * ip = NULL; + pam_get_item(pamh, PAM_RHOST, ( const void ** ) &ip); + if(ip == NULL) + ip = ""; + return ip; + } + + rublon::NonOwningPtr< const char > username() const { + // pam_get_user + } +}; diff --git a/PAM/ssh/src/rublon.hpp b/PAM/ssh/src/rublon.hpp new file mode 100644 index 0000000..1eb1da4 --- /dev/null +++ b/PAM/ssh/src/rublon.hpp @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "CoreHandler.hpp" +#include "json.hpp" +#include "pam.hpp" + +#include +#include + +namespace rublon { + +enum class PamAction { accept, decline }; + +template < typename Impl > +class AuthenticationStep { + public: + template < typename Hander_t > + auto fire(const CoreHandlerInterface< Hander_t > & coreHandler) { + // log step + // debugLog("Starting %s step", static_cast(this)->stepName ); + return static_cast< Impl * >(this)->handle(coreHandler); + } +}; + +template < typename PamInfo_t = PAMInfo > +class Method : public AuthenticationStep< Method< PamInfo_t > > { + std::string tid; + + public: + Method() {} +}; + +template < typename PamInfo_t = PAMInfo > +class Init : public AuthenticationStep< Init< PamInfo_t > > { + const char * apiPath = "/api/transaction/init"; + const std::string & systemToken; + + PamInfo_t pamInfo; + + public: + const char * stepName = "Initialization"; + + Init(const rublon::Configuration & config) : systemToken{config.parameters.systemToken} {} + + /// TODO add core handler interface + template < typename Hander_t > + std::variant< Method< PamInfo_t >, PamAction > handle(const CoreHandlerInterface< Hander_t > & handler) const { + char _buffer[1024]; + std::pmr::monotonic_buffer_resource mr{_buffer, 1024}; + + RapidJSONPMRAlloc alloc{&mr}; + Document body{rapidjson::kObjectType, &alloc}; + + body.AddMember("systemToken", Value{systemToken.c_str(), alloc}, alloc); + body.AddMember("username", Value{pamInfo.username().get(), alloc}, alloc); + body.AddMember("userEmail", "bwi@rublon.com", alloc); + + Value params{rapidjson::kObjectType}; + params.AddMember("userIP", Value{pamInfo.ip().get(), alloc}, alloc); + params.AddMember("appVer", "v.1.6", alloc); /// TODO add version to cmake + params.AddMember("os", "Ubuntu 23.04", alloc); /// TODO add version to cmake + + body.AddMember("params", std::move(params), alloc); + + auto response = handler.request(apiPath, body); + + if(response.has_value()) { + std::cout << response.value()["response"]["tid"].GetString(); + + return Method< PamInfo_t >{ + + }; + + } else { + // mostly connectio errors + switch(response.error().errorClass) { + case CoreHandlerError::ErrorClass::BadSigature: + return PamAction::decline; /// TODO accept? + case CoreHandlerError::ErrorClass::CoreException: + return PamAction::decline; /// TODO accept? + case CoreHandlerError::ErrorClass::ConnectionError: + return PamAction::decline; /// TODO accept? + case CoreHandlerError::ErrorClass::BrokenData: + return PamAction::decline; + } + } + + return {PamAction::decline}; + } +}; + +class ConfirmCode : public AuthenticationStep< ConfirmCode > { + public: + ConfirmCode(const Configuration & /*config*/) {} +}; + +class VerifySSH : public AuthenticationStep< VerifySSH > { + public: + VerifySSH(const Configuration & /*config*/) {} +}; + +class Credentials : public AuthenticationStep< Credentials > { + public: + Credentials(const Configuration & /*config*/) {} +}; + +} // namespace rublon diff --git a/PAM/ssh/src/sign.hpp b/PAM/ssh/src/sign.hpp new file mode 100644 index 0000000..89459ee --- /dev/null +++ b/PAM/ssh/src/sign.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +namespace rublon { + +inline std::array< char, 64 > signData(std::string_view data, std::string_view secretKey) { + std::array< char, 64 > xRublon; + unsigned char md[EVP_MAX_MD_SIZE] = {0}; + unsigned int md_len; + HMAC(EVP_sha256(), secretKey.data(), secretKey.size(), ( unsigned const char * ) data.data(), data.size(), md, &md_len); + int i; + for(i = 0; i < 32; i++) + sprintf(&xRublon[i * 2], "%02x", ( unsigned int ) md[i]); + return xRublon; +} + +} // namespace rublon diff --git a/PAM/ssh/src/span.hpp b/PAM/ssh/src/span.hpp new file mode 100644 index 0000000..ac176e9 --- /dev/null +++ b/PAM/ssh/src/span.hpp @@ -0,0 +1,329 @@ +#pragma once + +#include // for std::array, etc. +#include // for assert +#include // for std::size_t, etc. +#include // for std::reverse_iterator, etc. +#include // for std::enable_if, etc. + +namespace rublon { + +#define CONSTRAINT(...) std::enable_if_t< (__VA_ARGS__), int > = 0 +#define EXPECTS(...) assert((__VA_ARGS__)) + +// constants + +// equivalent to std::numeric_limits::max() +inline constexpr std::size_t dynamic_extent = -1; + +// class template span + +template < class T, std::size_t N = dynamic_extent > +class span; + +namespace span_detail { + + // detect specializations of span + + template < class T > + struct is_span : std::false_type {}; + + template < class T, std::size_t N > + struct is_span< span< T, N > > : std::true_type {}; + + template < class T > + inline constexpr bool is_span_v = is_span< T >::value; + + // detect specializations of std::array + + template < class T > + struct is_array : std::false_type {}; + + template < class T, std::size_t N > + struct is_array< std::array< T, N > > : std::true_type {}; + + template < class T > + inline constexpr bool is_array_v = is_array< T >::value; + + // ADL-aware data() and size() + + template < class C > + constexpr decltype(auto) my_data(C & c) { + return std::data(c); + } + + template < class C > + constexpr decltype(auto) my_size(C & c) { + return std::size(c); + } + + // detect container + + template < class C, class = void > + struct is_cont : std::false_type {}; + + template < class C > + struct is_cont< C, + std::void_t< std::enable_if_t< !is_span_v< C > >, + std::enable_if_t< !is_array_v< C > >, + std::enable_if_t< !std::is_array_v< C > >, + decltype(std::data(std::declval< C >())), + decltype(std::size(std::declval< C >())) > > : std::true_type {}; + + template < class C > + inline constexpr bool is_cont_v = is_cont< C >::value; +} // namespace span_detail + +template < class T, std::size_t N > +class span { + public: + // constants and types + + using element_type = T; + using value_type = std::remove_cv_t< T >; + using index_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using pointer = T *; + using const_pointer = const T *; + using reference = T &; + using const_reference = const T &; + + using iterator = T *; + using const_iterator = const T *; + using reverse_iterator = std::reverse_iterator< iterator >; + using const_reverse_iterator = std::reverse_iterator< const_iterator >; + + static constexpr index_type extent = N; + + // constructors, copy, and assignment + + // LWG 3198 applied + constexpr span() noexcept : size_{0}, data_{nullptr} { + static_assert(N == dynamic_extent || N == 0); + } + + constexpr span(T * ptr, index_type n) : size_{n}, data_{ptr} { + EXPECTS(N == dynamic_extent || N == n); + } + + constexpr span(T * first, T * last) : size_{last - first}, data_{first} { + EXPECTS(N == dynamic_extent || last - first = N); + } + + template < std::size_t M, + CONSTRAINT(N == dynamic_extent || + N == M && + std::is_convertible_v< std::remove_pointer_t< decltype(span_detail::my_data(std::declval< T (&)[M] >())) > (*)[], T (*)[] >) > + constexpr span(T (&arr)[M]) noexcept : size_{M}, data_{arr} {} + + template < std::size_t M, + CONSTRAINT(N == dynamic_extent || + N == M && + std::is_convertible_v< std::remove_pointer_t< decltype(span_detail::my_data(std::declval< T (&)[M] >())) > (*)[], T (*)[] >) > + constexpr span(std::array< value_type, M > & arr) noexcept : size_{M}, data_{arr.data()} {} + + template < std::size_t M, + CONSTRAINT(N == dynamic_extent || + N == M && + std::is_convertible_v< std::remove_pointer_t< decltype(span_detail::my_data(std::declval< T (&)[M] >())) > (*)[], T (*)[] >) > + constexpr span(const std::array< value_type, M > & arr) noexcept : size_{M}, data_{arr.data()} {} + + template < class Cont, + CONSTRAINT(N == dynamic_extent && span_detail::is_cont_v< Cont > && + std::is_convertible_v< std::remove_pointer_t< decltype(span_detail::my_data(std::declval< Cont >())) > (*)[], T (*)[] >) > + constexpr span(Cont & c) : size_{span_detail::my_size(c)}, data_{span_detail::my_data(c)} {} + + template < class Cont, + CONSTRAINT(N == dynamic_extent && span_detail::is_cont_v< Cont > && + std::is_convertible_v< std::remove_pointer_t< decltype(span_detail::my_data(std::declval< Cont >())) > (*)[], T (*)[] >) > + constexpr span(const Cont & c) : size_{span_detail::my_size(c)}, data_{span_detail::my_data(c)} {} + + constexpr span(const span & other) noexcept = default; + + // template < class U, std::size_t M, CONSTRAINT(N == dynamic_extent || N == M && std::is_convertible_v< U (*)[], T (*)[] >) > + // constexpr span(const span< U, M > & s) noexcept : size_{s.size()}, data_{s.data()} {} + + ~span() noexcept = default; + + constexpr span & operator=(const span & other) noexcept = default; + + // subviews + + template < std::size_t Cnt > + constexpr span< T, Cnt > first() const { + assert(Cnt <= size()); + return {data(), Cnt}; + } + + template < std::size_t Cnt > + constexpr span< T, Cnt > last() const { + assert(Cnt <= size()); + return {data() + (size() - Cnt), Cnt}; + } + + template < std::size_t Off, std::size_t Cnt = dynamic_extent > + constexpr auto subspan() const { + assert(Off <= size() && (Cnt == dynamic_extent || Off + Cnt <= size())); + if constexpr(Cnt != dynamic_extent) + return span< T, Cnt >{data() + Off, Cnt}; + else if constexpr(N != dynamic_extent) + return span< T, N - Off >{data() + Off, size() - Off}; + else + return span< T, dynamic_extent >{data() + Off, size() - Off}; + } + + constexpr span< T, dynamic_extent > first(index_type cnt) const { + assert(cnt <= size()); + return {data(), cnt}; + } + + constexpr span< T, dynamic_extent > last(index_type cnt) const { + assert(cnt <= size()); + return {data() + (size() - cnt), cnt}; + } + + constexpr span< T, dynamic_extent > subspan(index_type off, index_type cnt = dynamic_extent) const { + assert(off <= size() && (cnt == dynamic_extent || off + cnt <= size())); + return {data() + off, cnt == dynamic_extent ? size() - off : cnt}; + } + + // observers + + constexpr index_type size() const noexcept { + return size_; + } + + constexpr index_type size_bytes() const noexcept { + return size() * sizeof(T); + } + + [[nodiscard]] constexpr bool empty() const noexcept { + return size() == 0; + } + + // element access + + constexpr reference operator[](index_type idx) const { + assert(idx < size()); + return *(data() + idx); + } + + constexpr reference front() const { + assert(!empty()); + return *data(); + } + + constexpr reference back() const { + assert(!empty()); + return *(data() + (size() - 1)); + } + + constexpr pointer data() const noexcept { + return data_; + } + + // iterator support + + constexpr iterator begin() const noexcept { + return data(); + } + + constexpr iterator end() const noexcept { + return data() + size(); + } + + constexpr const_iterator cbegin() const noexcept { + return data(); + } + + constexpr const_iterator cend() const noexcept { + return data() + size(); + } + + constexpr reverse_iterator rbegin() const noexcept { + return reverse_iterator{end()}; + } + + constexpr reverse_iterator rend() const noexcept { + return reverse_iterator{begin()}; + } + + constexpr const_reverse_iterator crbegin() const noexcept { + return reverse_iterator{cend()}; + } + + constexpr const_reverse_iterator crend() const noexcept { + return reverse_iterator{cbegin()}; + } + + friend constexpr iterator begin(span s) noexcept { + return s.begin(); + } + + friend constexpr iterator end(span s) noexcept { + return s.end(); + } + + private: + pointer data_; + index_type size_; +}; + +// deduction guide + +template < class T, std::size_t N > +span(T (&)[N]) -> span< T, N >; + +template < class T, std::size_t N > +span(std::array< T, N > &) -> span< T, N >; + +template < class T, std::size_t N > +span(const std::array< T, N > &) -> span< const T, N >; + +template < class Cont > +span(Cont &) -> span< typename Cont::value_type >; + +template < class Cont > +span(const Cont &) -> span< const typename Cont::value_type >; + +// views of objects representation + +template < class T, std::size_t N > +auto as_bytes(span< T, N > s) noexcept -> span< const std::byte, N == dynamic_extent ? dynamic_extent : sizeof(T) * N > { + return {reinterpret_cast< const std::byte * >(s.data()), s.size_bytes()}; +} + +template < class T, std::size_t N, CONSTRAINT(!std::is_const_v< T >) > +auto as_writable_bytes(span< T, N > s) noexcept -> span< std::byte, N == dynamic_extent ? dynamic_extent : sizeof(T) * N > { + return {reinterpret_cast< std::byte * >(s.data()), s.size_bytes()}; +} +} // namespace rublon + +namespace std { + +// tuple interface +// the primary template declarations are included in + +template < class T, std::size_t N > +struct tuple_size< rublon::span< T, N > > : std::integral_constant< std::size_t, N > {}; + +// not defined +template < class T > +struct tuple_size< rublon::span< T, rublon::dynamic_extent > >; + +template < std::size_t I, class T, std::size_t N > +struct tuple_element< I, rublon::span< T, N > > { + static_assert(N != rublon::dynamic_extent && I < N); + using type = T; +}; + +template < std::size_t I, class T, std::size_t N > +constexpr T & get(rublon::span< T, N > s) noexcept { + static_assert(N != rublon::dynamic_extent && I < N); + return s[I]; +} +} // namespace std + +#undef CONSTRAINT +#undef EXPECTS diff --git a/PAM/ssh/src/utils.hpp b/PAM/ssh/src/utils.hpp new file mode 100644 index 0000000..53c70c7 --- /dev/null +++ b/PAM/ssh/src/utils.hpp @@ -0,0 +1,107 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace rublon { + +template < typename T > +class NonOwningPtr { + T * object; + + public: + constexpr NonOwningPtr(T * obj) : object{obj} {} + + constexpr T * get() noexcept { + assert(object != nullptr); + return object; + } + constexpr const T * get() const noexcept { + assert(object != nullptr); + return object; + } + constexpr operator const T *() const noexcept { + return get(); + } + constexpr operator T *() noexcept { + return get(); + } +}; + +namespace details { + + inline bool to_bool(std::string_view value) { + auto * buf = ( char * ) alloca(value.size()); + buf[value.size()] = '\0'; + auto asciitolower = [](char in) { return in - ((in <= 'Z' && in >= 'A') ? ('Z' - 'z') : 0); }; + + std::transform(value.cbegin(), value.cend(), buf, asciitolower); + return strcmp(buf, "true") == 0; + }; + + static inline std::string_view ltrim(std::string_view s) { + while(s.length() && std::isspace(*s.begin())) + s.remove_prefix(1); + return s; + } + + static inline std::string_view rtrim(std::string_view s) { + while(s.length() && std::isspace(*s.rbegin())) + s.remove_suffix(1); + return s; + } + + static inline std::string_view trim(std::string_view s) { + return ltrim(rtrim(s)); + } + + inline std::map< std::string, std::string > headers(std::string_view data) { + std::map< std::string, std::string > headers{}; + + std::string tmp{}; + std::istringstream resp{}; + resp.rdbuf()->pubsetbuf(const_cast< char * >(data.data()), data.size()); + + while(std::getline(resp, tmp)) { + auto line = std::string_view(tmp); + auto index = tmp.find(':', 0); + if(index != std::string::npos) { + headers.insert({std::string{trim(line.substr(0, index))}, std::string{trim(line.substr(index + 1))}}); + } + } + + return headers; + } + + namespace pmr { + inline std::pmr::map< std::pmr::string, std::pmr::string > headers(std::pmr::memory_resource * mr, std::string_view data) { + char _buf[1024]; + std::pmr::monotonic_buffer_resource tmr{_buf, sizeof(_buf)}; + std::pmr::map< std::pmr::string, std::pmr::string > headers{mr}; + + std::pmr::string tmp{&tmr}; + std::istringstream resp{}; + resp.rdbuf()->pubsetbuf(const_cast< char * >(data.data()), data.size()); + + while(std::getline(resp, tmp) && !(trim(tmp).empty())) { + auto line = std::string_view(tmp); + auto index = tmp.find(':', 0); + if(index != std::string::npos) { + headers.insert({std::pmr::string{trim(line.substr(0, index)), mr}, std::pmr::string{trim(line.substr(index + 1)), mr}}); + } + } + + return headers; + } + } // namespace pmr + +} // namespace details + +} // namespace rublon diff --git a/PAM/ssh/tests/CMakeLists.txt b/PAM/ssh/tests/CMakeLists.txt new file mode 100644 index 0000000..3270f6e --- /dev/null +++ b/PAM/ssh/tests/CMakeLists.txt @@ -0,0 +1,24 @@ +enable_testing() + +include(FetchContent) + +## Project-wide setup +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED YES) +set(CMAKE_CXX_EXTENSIONS NO) + +# Externally provided libraries +FetchContent_Declare(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main) + +FetchContent_Declare(googlebenchmark + GIT_REPOSITORY https://github.com/google/benchmark.git + GIT_TAG main) + +FetchContent_MakeAvailable( + googletest + googlebenchmark) + +add_executable(rublon-tests utilsTests.cpp rublonTests.cpp core_handler_tests.cpp init_test.cpp) +target_link_libraries(rublon-tests GTest::gmock_main -lssl -lcrypto) diff --git a/PAM/ssh/tests/core_handler_mock.hpp b/PAM/ssh/tests/core_handler_mock.hpp new file mode 100644 index 0000000..e69de29 diff --git a/PAM/ssh/tests/core_handler_tests.cpp b/PAM/ssh/tests/core_handler_tests.cpp new file mode 100644 index 0000000..031edef --- /dev/null +++ b/PAM/ssh/tests/core_handler_tests.cpp @@ -0,0 +1,67 @@ +#include +#include + +#include +#include + +#include "../src/CoreHandler.hpp" +#include "http_mock.hpp" + +class CoreHandlerTestable : public rublon::CoreHandler< HttpHandlerMock > { + public: + CoreHandlerTestable() : rublon::CoreHandler< HttpHandlerMock >{conf} {} + auto & _http() { + return http; + } +}; + +class CoreHandlerTests : public testing::Test { + public: + CoreHandlerTests() : http{sut._http()} { + doc.SetObject(); + } + + CoreHandlerTestable sut; + HttpHandlerMock & http; + + rublon::Document doc; +}; + +using namespace testing; + +MATCHER(HasValue, "") { + return arg.has_value(); +} + +bool operator==(const rublon::CoreHandlerError & lhs, const rublon::CoreHandlerError & rhs) { + return lhs.errorClass == rhs.errorClass; +} + +MATCHER_P(Unexpected, error, "") { + return arg.error() == error; +} + +TEST_F(CoreHandlerTests, coreShouldSetConnectionErrorOnBrokenConnection) { + EXPECT_CALL(http, request(_, _)).WillOnce(Return(std::optional< rublon::Response >{})); + EXPECT_THAT(sut.request("", doc), // + AllOf(Not(HasValue()), Unexpected(rublon::CoreHandlerError::ConnectionError))); +} + +TEST_F(CoreHandlerTests, coreShouldCheckSignatureAndReturnBadSignatureBeforeAnythingElse) { + EXPECT_CALL(http, request(_, _)).WillOnce(Return((rublon::Response) http.brokenSignature().brokenBody())); + EXPECT_THAT(sut.request("", doc), // + AllOf(Not(HasValue()), Unexpected(rublon::CoreHandlerError::BadSigature))); +} + +TEST_F(CoreHandlerTests, coreShouldSetBrokenDataWhenResultIsNotAvailable) { + EXPECT_CALL(http, request(_, _)).WillOnce(Return(( rublon::Response ) http.brokenBody())); + EXPECT_THAT(sut.request("", doc), // + AllOf(Not(HasValue()), Unexpected(rublon::CoreHandlerError::BrokenData))); +} + +TEST_F(CoreHandlerTests, coreSignatureIsBeingChecked) { + EXPECT_CALL(http, request(Eq(conf.parameters.apiServer + "/path"), _)).WillOnce(Return(( rublon::Response ) http.statusPending())); + auto val = sut.request("/path", doc); + + EXPECT_TRUE(val.value().IsObject()); +} diff --git a/PAM/ssh/tests/core_response_generator.hpp b/PAM/ssh/tests/core_response_generator.hpp new file mode 100644 index 0000000..a8f7d01 --- /dev/null +++ b/PAM/ssh/tests/core_response_generator.hpp @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include + +namespace io { +template < class X > +using is_string = typename std::enable_if_t< std::is_same_v< X, std::string > >; + +template < class X > +using is_not_string = typename std::enable_if_t< !std::is_same_v< X, std::string > >; + +template < class T, class = is_not_string< T > > +constexpr T forward_or_transform(T t) { + return std::forward< T >(t); +} + +template < class T, class = is_string< T > > +constexpr const char * forward_or_transform(T t) { + return t.c_str(); +} + +template < class... Ti > +int sprintf(char * _s, const std::string & format, Ti... t) { + return std::sprintf(_s, format.c_str(), forward_or_transform(t)...); +} +} // namespace io + + +namespace { +std::string gen_random(const int len) { + static const char alphanum[] = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::string tmp_s; + tmp_s.resize(len); + + std::for_each(tmp_s.begin(), tmp_s.end(), [](auto & chr) { chr = alphanum[rand() % (sizeof(alphanum) - 1)]; }); + + return tmp_s; +} + +static constexpr const char * result_ok_template = + R"json({"status":"OK","result":{"tid":"%s","status":"%s","companyName":"%s","applicationName":"%s","methods":[%s]}})json"; + +static constexpr const char * result_broken_template = R"json({"some":"random","json":"notrublon"})json"; + +} // namespace + +class CoreResponseGenerator { + public: + std::string generateBody() { + std::array< char, 2048 > _buf; + io::sprintf(_buf.data(), + generateBrokenData ? result_broken_template : result_ok_template, + tid, + status, + companyName, + applicationName, + print_methods()); + return _buf.data(); + } + + std::string print_methods() { + std::string ret; + for(const auto & m : methods) + ret += "\"" + m + "\","; + ret.pop_back(); + return ret; + } + + static std::string generateTid() { + return gen_random(32); + } + + std::string tid{generateTid()}; + std::string status; + std::string companyName{"rublon"}; + std::string applicationName{"test_app"}; + std::set< std::string > methods{"email", "totp", "qrcode", "push"}; + + bool skipSignatureGeneration{false}; + bool generateBrokenData{false}; +}; diff --git a/PAM/ssh/tests/http_mock.hpp b/PAM/ssh/tests/http_mock.hpp new file mode 100644 index 0000000..9bb0bf8 --- /dev/null +++ b/PAM/ssh/tests/http_mock.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include + +#include +#include + +#include "../src/curl.hpp" +#include "../src/configuration.hpp" + +#include "core_response_generator.hpp" + +namespace { +rublon::Configuration conf{rublon::Configuration::Parameters{// + "320BAB778C4D4262B54CD243CDEFFAFD", + "39e8d771d83a2ed3cc728811911c25", + "https://staging-core.rublon.net", + 1, + true, + true, + false}}; +} // namespace + +class HttpHandlerMock { + auto signResponse(rublon::Response & res) { + const auto & sign = + skipSignatureGeneration ? std::array< char, 64 >{} : rublon::signData(res.body, conf.parameters.secretKey.c_str()); + res.headers["x-rublon-signature"] = sign.data(); + } + + public: + MOCK_METHOD(std::optional< rublon::Response >, request, ( std::string_view, const rublon::Request & ), (const)); + + HttpHandlerMock & statusPending() { + gen.status = "pending"; + return *this; + } + + HttpHandlerMock & brokenBody() { + gen.generateBrokenData = true; + return *this; + } + + HttpHandlerMock & brokenSignature() { + skipSignatureGeneration = true; + return *this; + } + + operator rublon::Response() { + rublon::Response res; + res.body = gen.generateBody(); + + signResponse(res); + + return res; + } + + bool skipSignatureGeneration; + CoreResponseGenerator gen; +}; diff --git a/PAM/ssh/tests/init_test.cpp b/PAM/ssh/tests/init_test.cpp new file mode 100644 index 0000000..ee934b7 --- /dev/null +++ b/PAM/ssh/tests/init_test.cpp @@ -0,0 +1,58 @@ +#include +#include + +#include "../src/rublon.hpp" + +#include "core_response_generator.hpp" + +using namespace rublon; +using namespace testing; + +namespace { + Configuration conf; +} + +class CoreHandlerMock : public CoreHandlerInterface< CoreHandlerMock > { + public: + CoreHandlerMock() {} + + MOCK_METHOD(( tl::expected< Document, CoreHandlerError > ), request, ( std::string_view, const Document & ), (const)); + + CoreHandlerMock & statusPending() { + gen.status = "pending"; + return *this; + } + + CoreHandlerMock & brokenBody() { + gen.generateBrokenData = true; + return *this; + } + + operator tl::expected< Document, CoreHandlerError >() { + auto body = gen.generateBody(); + + rublon::Document doc; + doc.Parse(body.c_str()); + + return doc; + } + + CoreResponseGenerator gen; +}; + +class RublonHttpInitTest : public testing::Test { + public: + CoreHandlerMock coreHandler; + Init<> sut{conf}; +}; + +using CoreReturn = tl::expected< Document, CoreHandlerError >; + +TEST_F(RublonHttpInitTest, initializationSendsRequestOnGoodPath) { + EXPECT_CALL(coreHandler, request("/api/transaction/init", _)).WillOnce(Return(tl::unexpected{CoreHandlerError{CoreHandlerError::BadSigature}})); + sut.handle(coreHandler); +} + +TEST_F(RublonHttpInitTest, returnMethods){ + +} diff --git a/PAM/ssh/tests/rublonTests.cpp b/PAM/ssh/tests/rublonTests.cpp new file mode 100644 index 0000000..46103fd --- /dev/null +++ b/PAM/ssh/tests/rublonTests.cpp @@ -0,0 +1,7 @@ +#include +#include + +#include "../src/rublon.hpp" + +using namespace testing; + diff --git a/PAM/ssh/tests/utilsTests.cpp b/PAM/ssh/tests/utilsTests.cpp new file mode 100644 index 0000000..aaa1e6d --- /dev/null +++ b/PAM/ssh/tests/utilsTests.cpp @@ -0,0 +1,90 @@ +#include "gmock/gmock.h" +#include +#include +#include +#include + +#include "../src/utils.hpp" + +using namespace rublon; +using namespace testing; + +TEST(Utils, toBoolReturnsTrueWhenStringIsPassed) { + EXPECT_TRUE(details::to_bool("TrUe")); +} + +TEST(Utils, toBoolReturnsFalseWhenStringIsPassed) { + EXPECT_FALSE(details::to_bool("False")); +} + +std::string response = R"http(HTTP/2 200 +date: Thu, 22 Jun 2023 13:24:58 GMT +content-type: application/json +server: nginx +cache-control: no-cache, private +x-rublon-signature: 1a01558bedaa2dd92ff659fb8ee3c65a89163d63e312fcb9b6f60463cce864d7 +x-ratelimit-limit: 300 +x-ratelimit-remaining: 299 + +{"status":"OK","result":{"tid":"2039132542F6465691BF8C41D7CC38C5","status":"pending","companyName":"rublon","applicationName":"Bartoszek_SSH","methods":["email","totp","qrcode","push"]}})http"; + +static inline std::string_view ltrim(std::string_view s) { + while(std::isspace(*s.begin())) + s.remove_prefix(1); + return s; +} + +static inline std::string_view rtrim(std::string_view s) { + while(std::isspace(*s.rbegin())) + s.remove_suffix(1); + return s; +} + +static inline std::string_view trim(std::string_view s) { + return ltrim(rtrim(s)); +} + +std::pmr::map< std::pmr::string, std::pmr::string > headers(std::pmr::memory_resource * mr, std::string_view data) { + std::pmr::map< std::pmr::string, std::pmr::string > headers{mr}; + + std::pmr::string tmp{mr}; + tmp.reserve(256); + std::istringstream resp{}; + resp.rdbuf()->pubsetbuf(const_cast< char * >(data.data()), data.size()); + + while(std::getline(resp, tmp) && !(trim(tmp).empty())) { + auto line = std::string_view(tmp); + auto index = tmp.find(':', 0); + if(index != std::string::npos) { + headers.insert({std::pmr::string{trim(line.substr(0, index)), mr}, std::pmr::string{trim(line.substr(index + 1)), mr}}); + } + } + + return headers; +} + +inline std::map< std::string, std::string > headers(std::string_view data) { + std::map< std::string, std::string > headers{}; + + std::string tmp{}; + std::istringstream resp{}; + resp.rdbuf()->pubsetbuf(const_cast< char * >(data.data()), data.size()); + + while(std::getline(resp, tmp) && !(trim(tmp).empty())) { + auto line = std::string_view(tmp); + auto index = tmp.find(':', 0); + if(index != std::string::npos) { + headers.insert({std::string{trim(line.substr(0, index))}, std::string{trim(line.substr(index + 1))}}); + } + } + + return headers; +} + +TEST(Utils, responseParser) { + auto sut = headers(response); + EXPECT_THAT(sut, + AllOf(Contains(Pair("date", "Thu, 22 Jun 2023 13:24:58 GMT")), + Contains(Pair("x-rublon-signature", "1a01558bedaa2dd92ff659fb8ee3c65a89163d63e312fcb9b6f60463cce864d7")), + Contains(Pair("x-ratelimit-remaining", "299")))); +}