diff --git a/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst b/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst new file mode 100644 index 00000000..44977c70 --- /dev/null +++ b/src/ros2_medkit_diagnostic_bridge/CHANGELOG.rst @@ -0,0 +1,19 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_diagnostic_bridge +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Forthcoming +----------- +* Initial rosdistro release +* Bridge node converting standard ROS 2 /diagnostics to FaultManager fault reports +* Severity mapping: + + * OK -> PASSED event (fault condition cleared) + * WARN -> WARN severity FAILED event + * ERROR -> ERROR severity FAILED event + * STALE -> CRITICAL severity FAILED event + +* Auto-generated fault codes from diagnostic names (UPPER_SNAKE_CASE) +* Custom name_to_code mappings via ROS parameters +* Stateless design: always sends PASSED for OK status (handles restarts) +* Contributors: Michal Faferek diff --git a/src/ros2_medkit_fault_manager/CHANGELOG.rst b/src/ros2_medkit_fault_manager/CHANGELOG.rst new file mode 100644 index 00000000..cd11a283 --- /dev/null +++ b/src/ros2_medkit_fault_manager/CHANGELOG.rst @@ -0,0 +1,50 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_fault_manager +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Forthcoming +----------- +* Initial rosdistro release +* Central fault management node with ROS 2 services: + + * ReportFault - report FAILED/PASSED events with debounce filtering + * GetFaults - query faults with filtering by severity, status, correlation + * ClearFault - clear/acknowledge faults + +* Debounce filtering with configurable thresholds: + + * FAILED events decrement counter, PASSED events increment + * Configurable confirmation_threshold (default: -1, immediate) + * Optional healing support (healing_enabled, healing_threshold) + * Time-based auto-confirmation (auto_confirm_after_sec) + * CRITICAL severity bypasses debounce + +* Dual storage backends: + + * SQLite persistent storage with WAL mode (default) + * In-memory storage for testing/lightweight deployments + +* Snapshot capture on fault confirmation: + + * Topic data captured as JSON with configurable topic resolution + * Priority: fault_specific > patterns > default_topics + * Stored in SQLite with indexed fault_code lookup + * Auto-cleanup on fault clear + +* Rosbag capture with ring buffer: + + * Configurable duration, post-fault recording, topic selection + * Lazy start mode (start on PREFAILED) or immediate + * Auto-cleanup of bag files, storage limits (max_bag_size_mb) + * GetRosbag service for bag file metadata + +* Fault correlation engine: + + * Hierarchical mode: root cause to symptom relationships + * Auto-cluster mode: group similar faults within time window + * YAML-based configuration with pattern wildcards + * Muted faults tracking, auto-clear on root cause resolution + +* FaultEvent publishing on ~/events topic for SSE streaming +* Wall clock timestamps (compatible with use_sim_time) +* Contributors: Bartosz Burda, Michal Faferek diff --git a/src/ros2_medkit_fault_reporter/CHANGELOG.rst b/src/ros2_medkit_fault_reporter/CHANGELOG.rst new file mode 100644 index 00000000..3e9bf5e9 --- /dev/null +++ b/src/ros2_medkit_fault_reporter/CHANGELOG.rst @@ -0,0 +1,22 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_fault_reporter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Forthcoming +----------- +* Initial rosdistro release +* FaultReporter client library with simple API: + + * report(fault_code, severity, description) - report FAILED events + * report_passed(fault_code) - report fault condition cleared + * High-severity faults (ERROR, CRITICAL) bypass local filtering + +* LocalFilter for per-fault-code threshold/window filtering: + + * Configurable threshold (default: 3 reports) and time window (default: 10s) + * Prevents flooding FaultManager with duplicate reports + * PASSED events always forwarded (bypass filtering) + +* Configuration via ROS parameters (filter_threshold, filter_window_sec) +* Thread-safe implementation with mutex-protected config access +* Contributors: Bartosz Burda, Michal Faferek diff --git a/src/ros2_medkit_gateway/CHANGELOG.rst b/src/ros2_medkit_gateway/CHANGELOG.rst new file mode 100644 index 00000000..87ad4769 --- /dev/null +++ b/src/ros2_medkit_gateway/CHANGELOG.rst @@ -0,0 +1,38 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_gateway +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Forthcoming +----------- +* Initial rosdistro release +* HTTP REST gateway for ros2_medkit diagnostics system +* SOVD-compatible entity discovery with four entity types: + + * Areas, Components, Apps, Functions + * HATEOAS links and capabilities in all responses + * Relationship endpoints (subareas, subcomponents, related-apps, hosts) + +* Three discovery modes: + + * Runtime-only: automatic ROS 2 graph introspection + * Manifest-only: YAML manifest with validation (11 rules) + * Hybrid: manifest as source of truth + runtime linking + +* REST API endpoints: + + * Fault management: GET/POST/DELETE /api/v1/faults + * Data access: topic sampling via GenericSubscription + * Operations: service calls and action goals via GenericClient + * Configuration: parameter get/set via ROS 2 parameter API + * Snapshots: GET /api/v1/faults/{code}/snapshots + * Rosbag: GET /api/v1/faults/{code}/snapshots/bag + +* Server-Sent Events (SSE) at /api/v1/faults/stream: + + * Multi-client support with thread-safe event queue + * Keepalive, Last-Event-ID reconnection, configurable max_clients + +* JWT-based authentication with configurable policies +* HTTPS/TLS support via OpenSSL and cpp-httplib +* Native C++ ROS 2 serialization via ros2_medkit_serialization (no CLI dependencies) +* Contributors: Bartosz Burda, Michal Faferek diff --git a/src/ros2_medkit_gateway/CMakeLists.txt b/src/ros2_medkit_gateway/CMakeLists.txt index b7337b27..fe5f5115 100644 --- a/src/ros2_medkit_gateway/CMakeLists.txt +++ b/src/ros2_medkit_gateway/CMakeLists.txt @@ -45,54 +45,18 @@ find_package(OpenSSL REQUIRED) # This enables the httplib::SSLServer class for HTTPS support add_compile_definitions(CPPHTTPLIB_OPENSSL_SUPPORT) -# Fetch header-only libraries -include(FetchContent) - -# Fetch tl::expected (C++11/14/17 compatible std::expected alternative) -# Disable tests and examples to avoid clang-tidy analyzing external code -set(EXPECTED_BUILD_TESTS OFF CACHE BOOL "" FORCE) -set(EXPECTED_BUILD_PACKAGE OFF CACHE BOOL "" FORCE) -fetchcontent_declare( - tl_expected - GIT_REPOSITORY https://github.com/TartanLlama/expected.git - GIT_TAG v1.3.1 - SYSTEM # Treat as system headers to suppress warnings - EXCLUDE_FROM_ALL # Don't build tests/examples -) -fetchcontent_getproperties(tl_expected) -if(NOT tl_expected_POPULATED) - fetchcontent_populate(tl_expected) - # Only add include directory, don't add_subdirectory to avoid install conflicts -endif() - -# Create interface library for tl::expected manually +# Vendored header-only libraries (no network access on ROS build farm) +# tl::expected v1.3.1 (CC0) - https://github.com/TartanLlama/expected add_library(tl_expected_iface INTERFACE) target_include_directories(tl_expected_iface SYSTEM INTERFACE - ${tl_expected_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src/vendored/tl_expected/include ) add_library(tl::expected ALIAS tl_expected_iface) -# Fetch jwt-cpp header-only library -# Disable tests and examples to avoid clang-tidy analyzing external code -set(JWT_CPP_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) -set(JWT_CPP_BUILD_TESTS OFF CACHE BOOL "" FORCE) -fetchcontent_declare( - jwt-cpp - GIT_REPOSITORY https://github.com/Thalhammer/jwt-cpp.git - GIT_TAG v0.7.0 - SYSTEM # Treat as system headers to suppress warnings - EXCLUDE_FROM_ALL # Don't build tests/examples -) -fetchcontent_getproperties(jwt-cpp) -if(NOT jwt-cpp_POPULATED) - fetchcontent_populate(jwt-cpp) - # Only add include directory, don't add_subdirectory to avoid install conflicts -endif() - -# Create interface library for jwt-cpp manually +# jwt-cpp v0.7.0 (MIT) - https://github.com/Thalhammer/jwt-cpp add_library(jwt_cpp_iface INTERFACE) target_include_directories(jwt_cpp_iface SYSTEM INTERFACE - ${jwt-cpp_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/src/vendored/jwt_cpp/include ) add_library(jwt-cpp::jwt-cpp ALIAS jwt_cpp_iface) @@ -217,20 +181,52 @@ if(BUILD_TESTING) set(ament_cmake_clang_format_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-format") set(ament_cmake_clang_tidy_CONFIG_FILE "${CMAKE_CURRENT_SOURCE_DIR}/../../.clang-tidy") - # Limit clang-tidy to only report issues from our source files (not FetchContent deps) + # Limit clang-tidy to only report issues from our source files (not vendored deps) set(ament_cmake_clang_tidy_HEADER_FILTER "^${CMAKE_CURRENT_SOURCE_DIR}/(include|src|test)/") - # Exclude linters that don't work well with FetchContent dependencies: + # Exclude linters that don't work well with vendored dependencies: # - uncrustify/cpplint: conflicts with clang-format - # - copyright/lint_cmake: flags generated/fetched files in install/ + # - copyright/clang_format: we configure manually to skip src/vendored/ # - clang_tidy: we configure it manually below with increased timeout list(APPEND AMENT_LINT_AUTO_EXCLUDE ament_cmake_uncrustify ament_cmake_cpplint ament_cmake_clang_tidy + ament_cmake_copyright + ament_cmake_clang_format ) ament_lint_auto_find_test_dependencies() + # Configure copyright check to exclude vendored dependencies + find_package(ament_cmake_copyright REQUIRED) + set(VENDORED_FILES + "src/vendored/jwt_cpp/include/jwt-cpp/base.h" + "src/vendored/jwt_cpp/include/jwt-cpp/jwt.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/defaults.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/traits.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h" + "src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/traits.h" + "src/vendored/jwt_cpp/include/picojson/picojson.h" + "src/vendored/tl_expected/include/tl/expected.hpp" + ) + ament_copyright(EXCLUDE ${VENDORED_FILES}) + + # Configure clang-format to only check our source files (not vendored) + find_package(ament_cmake_clang_format REQUIRED) + file(GLOB_RECURSE _format_files + "include/*.h" "include/*.hpp" + "src/*.cpp" "src/*.h" "src/*.hpp" + "test/*.cpp" "test/*.h" "test/*.hpp" + ) + list(FILTER _format_files EXCLUDE REGEX ".*/vendored/.*") + ament_clang_format(${_format_files} + CONFIG_FILE "${ament_cmake_clang_format_CONFIG_FILE}" + ) + # Configure clang-tidy manually with increased timeout (1500s instead of default 300s) # This is needed because the project has many files and clang-tidy analysis takes time ament_clang_tidy( diff --git a/src/ros2_medkit_gateway/package.xml b/src/ros2_medkit_gateway/package.xml index 5bfe149d..9512fa76 100644 --- a/src/ros2_medkit_gateway/package.xml +++ b/src/ros2_medkit_gateway/package.xml @@ -18,8 +18,9 @@ action_msgs nlohmann-json-dev libcpp-httplib-dev - + + libssl-dev yaml_cpp_vendor ros2_medkit_msgs ros2_medkit_serialization diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/LICENSE b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/LICENSE new file mode 100644 index 00000000..7d583cec --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Dominik Thalhammer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/base.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/base.h new file mode 100644 index 00000000..fd3ee281 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/base.h @@ -0,0 +1,270 @@ +#ifndef JWT_CPP_BASE_H +#define JWT_CPP_BASE_H + +#include +#include +#include +#include +#include +#include + +#ifdef __has_cpp_attribute +#if __has_cpp_attribute(fallthrough) +#define JWT_FALLTHROUGH [[fallthrough]] +#endif +#endif + +#ifndef JWT_FALLTHROUGH +#define JWT_FALLTHROUGH +#endif + +namespace jwt { + /** + * \brief character maps when encoding and decoding + */ + namespace alphabet { + /** + * \brief valid list of character when working with [Base64](https://datatracker.ietf.org/doc/html/rfc4648#section-4) + * + * As directed in [X.509 Parameter](https://datatracker.ietf.org/doc/html/rfc7517#section-4.7) certificate chains are + * base64-encoded as per [Section 4 of RFC4648](https://datatracker.ietf.org/doc/html/rfc4648#section-4) + */ + struct base64 { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}}; + return data; + } + static const std::string& fill() { + static const std::string fill{"="}; + return fill; + } + }; + /** + * \brief valid list of character when working with [Base64URL](https://tools.ietf.org/html/rfc4648#section-5) + * + * As directed by [RFC 7519 Terminology](https://datatracker.ietf.org/doc/html/rfc7519#section-2) set the definition of Base64URL + * encoding as that in [RFC 7515](https://datatracker.ietf.org/doc/html/rfc7515#section-2) that states: + * + * > Base64 encoding using the URL- and filename-safe character set defined in + * > [Section 5 of RFC 4648 RFC4648](https://tools.ietf.org/html/rfc4648#section-5), with all trailing '=' characters omitted + */ + struct base64url { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + } + static const std::string& fill() { + static const std::string fill{"%3d"}; + return fill; + } + }; + namespace helper { + /** + * @brief A General purpose base64url alphabet respecting the + * [URI Case Normalization](https://datatracker.ietf.org/doc/html/rfc3986#section-6.2.2.1) + * + * This is useful in situations outside of JWT encoding/decoding and is provided as a helper + */ + struct base64url_percent_encoding { + static const std::array& data() { + static constexpr std::array data{ + {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'}}; + return data; + } + static const std::vector& fill() { + static const std::vector fill{"%3D", "%3d"}; + return fill; + } + }; + } // namespace helper + + inline uint32_t index(const std::array& alphabet, char symbol) { + auto itr = std::find_if(alphabet.cbegin(), alphabet.cend(), [symbol](char c) { return c == symbol; }); + if (itr == alphabet.cend()) { throw std::runtime_error("Invalid input: not within alphabet"); } + + return std::distance(alphabet.cbegin(), itr); + } + } // namespace alphabet + + /** + * \brief A collection of fellable functions for working with base64 and base64url + */ + namespace base { + + namespace details { + struct padding { + size_t count = 0; + size_t length = 0; + + padding() = default; + padding(size_t count, size_t length) : count(count), length(length) {} + + padding operator+(const padding& p) { return padding(count + p.count, length + p.length); } + + friend bool operator==(const padding& lhs, const padding& rhs) { + return lhs.count == rhs.count && lhs.length == rhs.length; + } + }; + + inline padding count_padding(const std::string& base, const std::vector& fills) { + for (const auto& fill : fills) { + if (base.size() < fill.size()) continue; + // Does the end of the input exactly match the fill pattern? + if (base.substr(base.size() - fill.size()) == fill) { + return padding{1, fill.length()} + + count_padding(base.substr(0, base.size() - fill.size()), fills); + } + } + + return {}; + } + + inline std::string encode(const std::string& bin, const std::array& alphabet, + const std::string& fill) { + size_t size = bin.size(); + std::string res; + + // clear incomplete bytes + size_t fast_size = size - size % 3; + for (size_t i = 0; i < fast_size;) { + uint32_t octet_a = static_cast(bin[i++]); + uint32_t octet_b = static_cast(bin[i++]); + uint32_t octet_c = static_cast(bin[i++]); + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += alphabet[(triple >> 0 * 6) & 0x3F]; + } + + if (fast_size == size) return res; + + size_t mod = size % 3; + + uint32_t octet_a = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_b = fast_size < size ? static_cast(bin[fast_size++]) : 0; + uint32_t octet_c = fast_size < size ? static_cast(bin[fast_size++]) : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + switch (mod) { + case 1: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += fill; + res += fill; + break; + case 2: + res += alphabet[(triple >> 3 * 6) & 0x3F]; + res += alphabet[(triple >> 2 * 6) & 0x3F]; + res += alphabet[(triple >> 1 * 6) & 0x3F]; + res += fill; + break; + default: break; + } + + return res; + } + + inline std::string decode(const std::string& base, const std::array& alphabet, + const std::vector& fill) { + const auto pad = count_padding(base, fill); + if (pad.count > 2) throw std::runtime_error("Invalid input: too much fill"); + + const size_t size = base.size() - pad.length; + if ((size + pad.count) % 4 != 0) throw std::runtime_error("Invalid input: incorrect total size"); + + size_t out_size = size / 4 * 3; + std::string res; + res.reserve(out_size); + + auto get_sextet = [&](size_t offset) { return alphabet::index(alphabet, base[offset]); }; + + size_t fast_size = size - size % 4; + for (size_t i = 0; i < fast_size;) { + uint32_t sextet_a = get_sextet(i++); + uint32_t sextet_b = get_sextet(i++); + uint32_t sextet_c = get_sextet(i++); + uint32_t sextet_d = get_sextet(i++); + + uint32_t triple = + (sextet_a << 3 * 6) + (sextet_b << 2 * 6) + (sextet_c << 1 * 6) + (sextet_d << 0 * 6); + + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + res += static_cast((triple >> 0 * 8) & 0xFFU); + } + + if (pad.count == 0) return res; + + uint32_t triple = (get_sextet(fast_size) << 3 * 6) + (get_sextet(fast_size + 1) << 2 * 6); + + switch (pad.count) { + case 1: + triple |= (get_sextet(fast_size + 2) << 1 * 6); + res += static_cast((triple >> 2 * 8) & 0xFFU); + res += static_cast((triple >> 1 * 8) & 0xFFU); + break; + case 2: res += static_cast((triple >> 2 * 8) & 0xFFU); break; + default: break; + } + + return res; + } + + inline std::string decode(const std::string& base, const std::array& alphabet, + const std::string& fill) { + return decode(base, alphabet, std::vector{fill}); + } + + inline std::string pad(const std::string& base, const std::string& fill) { + std::string padding; + switch (base.size() % 4) { + case 1: padding += fill; JWT_FALLTHROUGH; + case 2: padding += fill; JWT_FALLTHROUGH; + case 3: padding += fill; JWT_FALLTHROUGH; + default: break; + } + + return base + padding; + } + + inline std::string trim(const std::string& base, const std::string& fill) { + auto pos = base.find(fill); + return base.substr(0, pos); + } + } // namespace details + + template + std::string encode(const std::string& bin) { + return details::encode(bin, T::data(), T::fill()); + } + template + std::string decode(const std::string& base) { + return details::decode(base, T::data(), T::fill()); + } + template + std::string pad(const std::string& base) { + return details::pad(base, T::fill()); + } + template + std::string trim(const std::string& base) { + return details::trim(base, T::fill()); + } + } // namespace base +} // namespace jwt + +#endif diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/jwt.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/jwt.h new file mode 100644 index 00000000..b2b998a2 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/jwt.h @@ -0,0 +1,3655 @@ +#ifndef JWT_CPP_JWT_H +#define JWT_CPP_JWT_H + +#ifndef JWT_DISABLE_PICOJSON +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" +#endif + +#ifndef JWT_DISABLE_BASE64 +#include "base.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus > 201103L +#include +#endif + +#if __cplusplus >= 201402L +#ifdef __has_include +#if __has_include() +#include +#endif +#endif +#endif + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L // 3.0.0 +#define JWT_OPENSSL_3_0 +#elif OPENSSL_VERSION_NUMBER >= 0x10101000L // 1.1.1 +#define JWT_OPENSSL_1_1_1 +#elif OPENSSL_VERSION_NUMBER >= 0x10100000L // 1.1.0 +#define JWT_OPENSSL_1_1_0 +#elif OPENSSL_VERSION_NUMBER >= 0x10000000L // 1.0.0 +#define JWT_OPENSSL_1_0_0 +#endif + +#if defined(LIBRESSL_VERSION_NUMBER) +#if LIBRESSL_VERSION_NUMBER >= 0x3050300fL +#define JWT_OPENSSL_1_1_0 +#else +#define JWT_OPENSSL_1_0_0 +#endif +#endif + +#if defined(LIBWOLFSSL_VERSION_HEX) +#define JWT_OPENSSL_1_1_1 +#endif + +#ifndef JWT_CLAIM_EXPLICIT +#define JWT_CLAIM_EXPLICIT explicit +#endif + +/** + * \brief JSON Web Token + * + * A namespace to contain everything related to handling JSON Web Tokens, JWT for short, + * as a part of [RFC7519](https://tools.ietf.org/html/rfc7519), or alternatively for + * JWS (JSON Web Signature) from [RFC7515](https://tools.ietf.org/html/rfc7515) + */ +namespace jwt { + /** + * Default system time point in UTC + */ + using date = std::chrono::system_clock::time_point; + + /** + * \brief Everything related to error codes issued by the library + */ + namespace error { + struct signature_verification_exception : public std::system_error { + using system_error::system_error; + }; + struct signature_generation_exception : public std::system_error { + using system_error::system_error; + }; + struct rsa_exception : public std::system_error { + using system_error::system_error; + }; + struct ecdsa_exception : public std::system_error { + using system_error::system_error; + }; + struct token_verification_exception : public std::system_error { + using system_error::system_error; + }; + /** + * \brief Errors related to processing of RSA signatures + */ + enum class rsa_error { + ok = 0, + cert_load_failed = 10, + get_key_failed, + write_key_failed, + write_cert_failed, + convert_to_pem_failed, + load_key_bio_write, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided + }; + /** + * \brief Error category for RSA errors + */ + inline std::error_category& rsa_error_category() { + class rsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "rsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case rsa_error::ok: return "no error"; + case rsa_error::cert_load_failed: return "error loading cert into memory"; + case rsa_error::get_key_failed: return "error getting key from certificate"; + case rsa_error::write_key_failed: return "error writing key data in PEM format"; + case rsa_error::write_cert_failed: return "error writing cert data in PEM format"; + case rsa_error::convert_to_pem_failed: return "failed to convert key to pem"; + case rsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case rsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case rsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case rsa_error::no_key_provided: return "at least one of public or private key need to be present"; + default: return "unknown RSA error"; + } + } + }; + static rsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(rsa_error e) { return {static_cast(e), rsa_error_category()}; } + /** + * \brief Errors related to processing of RSA signatures + */ + enum class ecdsa_error { + ok = 0, + load_key_bio_write = 10, + load_key_bio_read, + create_mem_bio_failed, + no_key_provided, + invalid_key_size, + invalid_key, + create_context_failed + }; + /** + * \brief Error category for ECDSA errors + */ + inline std::error_category& ecdsa_error_category() { + class ecdsa_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "ecdsa_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case ecdsa_error::ok: return "no error"; + case ecdsa_error::load_key_bio_write: return "failed to load key: bio write failed"; + case ecdsa_error::load_key_bio_read: return "failed to load key: bio read failed"; + case ecdsa_error::create_mem_bio_failed: return "failed to create memory bio"; + case ecdsa_error::no_key_provided: + return "at least one of public or private key need to be present"; + case ecdsa_error::invalid_key_size: return "invalid key size"; + case ecdsa_error::invalid_key: return "invalid key"; + case ecdsa_error::create_context_failed: return "failed to create context"; + default: return "unknown ECDSA error"; + } + } + }; + static ecdsa_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(ecdsa_error e) { return {static_cast(e), ecdsa_error_category()}; } + + /** + * \brief Errors related to verification of signatures + */ + enum class signature_verification_error { + ok = 0, + invalid_signature = 10, + create_context_failed, + verifyinit_failed, + verifyupdate_failed, + verifyfinal_failed, + get_key_failed, + set_rsa_pss_saltlen_failed, + signature_encoding_failed + }; + /** + * \brief Error category for verification errors + */ + inline std::error_category& signature_verification_error_category() { + class verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_verification_error::ok: return "no error"; + case signature_verification_error::invalid_signature: return "invalid signature"; + case signature_verification_error::create_context_failed: + return "failed to verify signature: could not create context"; + case signature_verification_error::verifyinit_failed: + return "failed to verify signature: VerifyInit failed"; + case signature_verification_error::verifyupdate_failed: + return "failed to verify signature: VerifyUpdate failed"; + case signature_verification_error::verifyfinal_failed: + return "failed to verify signature: VerifyFinal failed"; + case signature_verification_error::get_key_failed: + return "failed to verify signature: Could not get key"; + case signature_verification_error::set_rsa_pss_saltlen_failed: + return "failed to verify signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed"; + case signature_verification_error::signature_encoding_failed: + return "failed to verify signature: i2d_ECDSA_SIG failed"; + default: return "unknown signature verification error"; + } + } + }; + static verification_error_cat cat; + return cat; + } + + inline std::error_code make_error_code(signature_verification_error e) { + return {static_cast(e), signature_verification_error_category()}; + } + + /** + * \brief Errors related to signature generation errors + */ + enum class signature_generation_error { + ok = 0, + hmac_failed = 10, + create_context_failed, + signinit_failed, + signupdate_failed, + signfinal_failed, + ecdsa_do_sign_failed, + digestinit_failed, + digestupdate_failed, + digestfinal_failed, + rsa_padding_failed, + rsa_private_encrypt_failed, + get_key_failed, + set_rsa_pss_saltlen_failed, + signature_decoding_failed + }; + /** + * \brief Error category for signature generation errors + */ + inline std::error_category& signature_generation_error_category() { + class signature_generation_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "signature_generation_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case signature_generation_error::ok: return "no error"; + case signature_generation_error::hmac_failed: return "hmac failed"; + case signature_generation_error::create_context_failed: + return "failed to create signature: could not create context"; + case signature_generation_error::signinit_failed: + return "failed to create signature: SignInit failed"; + case signature_generation_error::signupdate_failed: + return "failed to create signature: SignUpdate failed"; + case signature_generation_error::signfinal_failed: + return "failed to create signature: SignFinal failed"; + case signature_generation_error::ecdsa_do_sign_failed: return "failed to generate ecdsa signature"; + case signature_generation_error::digestinit_failed: + return "failed to create signature: DigestInit failed"; + case signature_generation_error::digestupdate_failed: + return "failed to create signature: DigestUpdate failed"; + case signature_generation_error::digestfinal_failed: + return "failed to create signature: DigestFinal failed"; + case signature_generation_error::rsa_padding_failed: + return "failed to create signature: EVP_PKEY_CTX_set_rsa_padding failed"; + case signature_generation_error::rsa_private_encrypt_failed: + return "failed to create signature: RSA_private_encrypt failed"; + case signature_generation_error::get_key_failed: + return "failed to generate signature: Could not get key"; + case signature_generation_error::set_rsa_pss_saltlen_failed: + return "failed to create signature: EVP_PKEY_CTX_set_rsa_pss_saltlen failed"; + case signature_generation_error::signature_decoding_failed: + return "failed to create signature: d2i_ECDSA_SIG failed"; + default: return "unknown signature generation error"; + } + } + }; + static signature_generation_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(signature_generation_error e) { + return {static_cast(e), signature_generation_error_category()}; + } + + /** + * \brief Errors related to token verification errors + */ + enum class token_verification_error { + ok = 0, + wrong_algorithm = 10, + missing_claim, + claim_type_missmatch, + claim_value_missmatch, + token_expired, + audience_missmatch + }; + /** + * \brief Error category for token verification errors + */ + inline std::error_category& token_verification_error_category() { + class token_verification_error_cat : public std::error_category { + public: + const char* name() const noexcept override { return "token_verification_error"; }; + std::string message(int ev) const override { + switch (static_cast(ev)) { + case token_verification_error::ok: return "no error"; + case token_verification_error::wrong_algorithm: return "wrong algorithm"; + case token_verification_error::missing_claim: return "decoded JWT is missing required claim(s)"; + case token_verification_error::claim_type_missmatch: + return "claim type does not match expected type"; + case token_verification_error::claim_value_missmatch: + return "claim value does not match expected value"; + case token_verification_error::token_expired: return "token expired"; + case token_verification_error::audience_missmatch: + return "token doesn't contain the required audience"; + default: return "unknown token verification error"; + } + } + }; + static token_verification_error_cat cat = {}; + return cat; + } + + inline std::error_code make_error_code(token_verification_error e) { + return {static_cast(e), token_verification_error_category()}; + } + + inline void throw_if_error(std::error_code ec) { + if (ec) { + if (ec.category() == rsa_error_category()) throw rsa_exception(ec); + if (ec.category() == ecdsa_error_category()) throw ecdsa_exception(ec); + if (ec.category() == signature_verification_error_category()) + throw signature_verification_exception(ec); + if (ec.category() == signature_generation_error_category()) throw signature_generation_exception(ec); + if (ec.category() == token_verification_error_category()) throw token_verification_exception(ec); + } + } + } // namespace error +} // namespace jwt + +namespace std { + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; + template<> + struct is_error_code_enum : true_type {}; +} // namespace std + +namespace jwt { + /** + * \brief A collection for working with certificates + * + * These _helpers_ are usefully when working with certificates OpenSSL APIs. + * For example, when dealing with JWKS (JSON Web Key Set)[https://tools.ietf.org/html/rfc7517] + * you maybe need to extract the modulus and exponent of an RSA Public Key. + */ + namespace helper { + /** + * \brief Handle class for EVP_PKEY structures + * + * Starting from OpenSSL 1.1.0, EVP_PKEY has internal reference counting. This handle class allows + * jwt-cpp to leverage that and thus safe an allocation for the control block in std::shared_ptr. + * The handle uses shared_ptr as a fallback on older versions. The behaviour should be identical between both. + */ + class evp_pkey_handle { + public: + constexpr evp_pkey_handle() noexcept = default; +#ifdef JWT_OPENSSL_1_0_0 + /** + * \brief Construct a new handle. The handle takes ownership of the key. + * \param key The key to store + */ + explicit evp_pkey_handle(EVP_PKEY* key) { m_key = std::shared_ptr(key, EVP_PKEY_free); } + + EVP_PKEY* get() const noexcept { return m_key.get(); } + bool operator!() const noexcept { return m_key == nullptr; } + explicit operator bool() const noexcept { return m_key != nullptr; } + + private: + std::shared_ptr m_key{nullptr}; +#else + /** + * \brief Construct a new handle. The handle takes ownership of the key. + * \param key The key to store + */ + explicit constexpr evp_pkey_handle(EVP_PKEY* key) noexcept : m_key{key} {} + evp_pkey_handle(const evp_pkey_handle& other) : m_key{other.m_key} { + if (m_key != nullptr && EVP_PKEY_up_ref(m_key) != 1) throw std::runtime_error("EVP_PKEY_up_ref failed"); + } +// C++11 requires the body of a constexpr constructor to be empty +#if __cplusplus >= 201402L + constexpr +#endif + evp_pkey_handle(evp_pkey_handle&& other) noexcept + : m_key{other.m_key} { + other.m_key = nullptr; + } + evp_pkey_handle& operator=(const evp_pkey_handle& other) { + if (&other == this) return *this; + decrement_ref_count(m_key); + m_key = other.m_key; + increment_ref_count(m_key); + return *this; + } + evp_pkey_handle& operator=(evp_pkey_handle&& other) noexcept { + if (&other == this) return *this; + decrement_ref_count(m_key); + m_key = other.m_key; + other.m_key = nullptr; + return *this; + } + evp_pkey_handle& operator=(EVP_PKEY* key) { + decrement_ref_count(m_key); + m_key = key; + increment_ref_count(m_key); + return *this; + } + ~evp_pkey_handle() noexcept { decrement_ref_count(m_key); } + + EVP_PKEY* get() const noexcept { return m_key; } + bool operator!() const noexcept { return m_key == nullptr; } + explicit operator bool() const noexcept { return m_key != nullptr; } + + private: + EVP_PKEY* m_key{nullptr}; + + static void increment_ref_count(EVP_PKEY* key) { + if (key != nullptr && EVP_PKEY_up_ref(key) != 1) throw std::runtime_error("EVP_PKEY_up_ref failed"); + } + static void decrement_ref_count(EVP_PKEY* key) noexcept { + if (key != nullptr) EVP_PKEY_free(key); + } +#endif + }; + + inline std::unique_ptr make_mem_buf_bio() { + return std::unique_ptr(BIO_new(BIO_s_mem()), BIO_free_all); + } + + inline std::unique_ptr make_mem_buf_bio(const std::string& data) { + return std::unique_ptr( +#if OPENSSL_VERSION_NUMBER <= 0x10100003L + BIO_new_mem_buf(const_cast(data.data()), static_cast(data.size())), BIO_free_all +#else + BIO_new_mem_buf(data.data(), static_cast(data.size())), BIO_free_all +#endif + ); + } + + inline std::unique_ptr make_evp_md_ctx() { + return +#ifdef JWT_OPENSSL_1_0_0 + std::unique_ptr(EVP_MD_CTX_create(), &EVP_MD_CTX_destroy); +#else + std::unique_ptr(EVP_MD_CTX_new(), &EVP_MD_CTX_free); +#endif + } + + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurred) + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw, + std::error_code& ec) { + ec.clear(); + auto certbio = make_mem_buf_bio(certstr); + auto keybio = make_mem_buf_bio(); + if (!certbio || !keybio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + std::unique_ptr cert( + PEM_read_bio_X509(certbio.get(), nullptr, nullptr, const_cast(pw.c_str())), X509_free); + if (!cert) { + ec = error::rsa_error::cert_load_failed; + return {}; + } + std::unique_ptr key(X509_get_pubkey(cert.get()), EVP_PKEY_free); + if (!key) { + ec = error::rsa_error::get_key_failed; + return {}; + } + if (PEM_write_bio_PUBKEY(keybio.get(), key.get()) == 0) { + ec = error::rsa_error::write_key_failed; + return {}; + } + char* ptr = nullptr; + auto len = BIO_get_mem_data(keybio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + return {ptr, static_cast(len)}; + } + + /** + * \brief Extract the public key of a pem certificate + * + * \param certstr String containing the certificate encoded as pem + * \param pw Password used to decrypt certificate (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline std::string extract_pubkey_from_cert(const std::string& certstr, const std::string& pw = "") { + std::error_code ec; + auto res = extract_pubkey_from_cert(certstr, pw, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Convert the certificate provided as DER to PEM. + * + * \param cert_der_str String containing the certificate encoded as base64 DER + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline std::string convert_der_to_pem(const std::string& cert_der_str, std::error_code& ec) { + ec.clear(); + + auto c_str = reinterpret_cast(cert_der_str.c_str()); + + std::unique_ptr cert( + d2i_X509(NULL, &c_str, static_cast(cert_der_str.size())), X509_free); + auto certbio = make_mem_buf_bio(); + if (!cert || !certbio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + + if (!PEM_write_bio_X509(certbio.get(), cert.get())) { + ec = error::rsa_error::write_cert_failed; + return {}; + } + + char* ptr = nullptr; + const auto len = BIO_get_mem_data(certbio.get(), &ptr); + if (len <= 0 || ptr == nullptr) { + ec = error::rsa_error::convert_to_pem_failed; + return {}; + } + + return {ptr, static_cast(len)}; + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode, + std::error_code& ec) { + ec.clear(); + const auto decoded_str = decode(cert_base64_der_str); + return convert_der_to_pem(decoded_str, ec); + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64 decode and return + * the results. + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param decode The function to decode the cert + * \throw rsa_exception if an error occurred + */ + template + std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, Decode decode) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Convert the certificate provided as DER to PEM. + * + * \param cert_der_str String containing the DER certificate + * \param decode The function to decode the cert + * \throw rsa_exception if an error occurred + */ + inline std::string convert_der_to_pem(const std::string& cert_der_str) { + std::error_code ec; + auto res = convert_der_to_pem(cert_der_str, ec); + error::throw_if_error(ec); + return res; + } + +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str, std::error_code& ec) { + auto decode = [](const std::string& token) { + return base::decode(base::pad(token)); + }; + return convert_base64_der_to_pem(cert_base64_der_str, std::move(decode), ec); + } + + /** + * \brief Convert the certificate provided as base64 DER to PEM. + * + * This is useful when using with JWKs as x5c claim is encoded as base64 DER. More info + * (here)[https://tools.ietf.org/html/rfc7517#section-4.7] + * + * \param cert_base64_der_str String containing the certificate encoded as base64 DER + * \throw rsa_exception if an error occurred + */ + inline std::string convert_base64_der_to_pem(const std::string& cert_base64_der_str) { + std::error_code ec; + auto res = convert_base64_der_to_pem(cert_base64_der_str, ec); + error::throw_if_error(ec); + return res; + } +#endif + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate encoded as pem + * \param password Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + ec.clear(); + auto pubkey_bio = make_mem_buf_bio(); + if (!pubkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password, ec); + if (ec) return {}; + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } + } + + evp_pkey_handle pkey(PEM_read_bio_PUBKEY( + pubkey_bio.get(), nullptr, nullptr, + (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` + if (!pkey) ec = error::rsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate or key encoded as pem + * \param password Password used to decrypt certificate or key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline evp_pkey_handle load_public_key_from_string(const std::string& key, const std::string& password = "") { + std::error_code ec; + auto res = load_public_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + auto privkey_bio = make_mem_buf_bio(); + if (!privkey_bio) { + ec = error::rsa_error::create_mem_bio_failed; + return {}; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::rsa_error::load_key_bio_write; + return {}; + } + evp_pkey_handle pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); + if (!pkey) ec = error::rsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \throw rsa_exception if an error occurred + */ + inline evp_pkey_handle load_private_key_from_string(const std::string& key, const std::string& password = "") { + std::error_code ec; + auto res = load_private_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate encoded as pem + * \param password Password used to decrypt certificate (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + ec.clear(); + auto pubkey_bio = make_mem_buf_bio(); + if (!pubkey_bio) { + ec = error::ecdsa_error::create_mem_bio_failed; + return {}; + } + if (key.substr(0, 27) == "-----BEGIN CERTIFICATE-----") { + auto epkey = helper::extract_pubkey_from_cert(key, password, ec); + if (ec) return {}; + const int len = static_cast(epkey.size()); + if (BIO_write(pubkey_bio.get(), epkey.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + } else { + const int len = static_cast(key.size()); + if (BIO_write(pubkey_bio.get(), key.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + } + + evp_pkey_handle pkey(PEM_read_bio_PUBKEY( + pubkey_bio.get(), nullptr, nullptr, + (void*)password.data())); // NOLINT(google-readability-casting) requires `const_cast` + if (!pkey) ec = error::ecdsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a public key from a string. + * + * The string should contain a pem encoded certificate or public key + * + * \param key String containing the certificate or key encoded as pem + * \param password Password used to decrypt certificate or key (leave empty if not encrypted) + * \throw ecdsa_exception if an error occurred + */ + inline evp_pkey_handle load_public_ec_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_public_ec_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \param ec error_code for error_detection (gets cleared if no error occurs) + */ + inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, const std::string& password, + std::error_code& ec) { + auto privkey_bio = make_mem_buf_bio(); + if (!privkey_bio) { + ec = error::ecdsa_error::create_mem_bio_failed; + return {}; + } + const int len = static_cast(key.size()); + if (BIO_write(privkey_bio.get(), key.data(), len) != len) { + ec = error::ecdsa_error::load_key_bio_write; + return {}; + } + evp_pkey_handle pkey( + PEM_read_bio_PrivateKey(privkey_bio.get(), nullptr, nullptr, const_cast(password.c_str()))); + if (!pkey) ec = error::ecdsa_error::load_key_bio_read; + return pkey; + } + + /** + * \brief Load a private key from a string. + * + * \param key String containing a private key as pem + * \param password Password used to decrypt key (leave empty if not encrypted) + * \throw ecdsa_exception if an error occurred + */ + inline evp_pkey_handle load_private_ec_key_from_string(const std::string& key, + const std::string& password = "") { + std::error_code ec; + auto res = load_private_ec_key_from_string(key, password, ec); + error::throw_if_error(ec); + return res; + } + + /** + * Convert a OpenSSL BIGNUM to a std::string + * \param bn BIGNUM to convert + * \return bignum as string + */ + inline +#ifdef JWT_OPENSSL_1_0_0 + std::string + bn2raw(BIGNUM* bn) +#else + std::string + bn2raw(const BIGNUM* bn) +#endif + { + std::string res(BN_num_bytes(bn), '\0'); + BN_bn2bin(bn, (unsigned char*)res.data()); // NOLINT(google-readability-casting) requires `const_cast` + return res; + } + /** + * Convert an std::string to a OpenSSL BIGNUM + * \param raw String to convert + * \return BIGNUM representation + */ + inline std::unique_ptr raw2bn(const std::string& raw) { + return std::unique_ptr( + BN_bin2bn(reinterpret_cast(raw.data()), static_cast(raw.size()), nullptr), + BN_free); + } + } // namespace helper + + /** + * \brief Various cryptographic algorithms when working with JWT + * + * JWT (JSON Web Tokens) signatures are typically used as the payload for a JWS (JSON Web Signature) or + * JWE (JSON Web Encryption). Both of these use various cryptographic as specified by + * [RFC7518](https://tools.ietf.org/html/rfc7518) and are exposed through the a [JOSE + * Header](https://tools.ietf.org/html/rfc7515#section-4) which points to one of the JWA (JSON Web + * Algorithms)(https://tools.ietf.org/html/rfc7518#section-3.1) + */ + namespace algorithm { + /** + * \brief "none" algorithm. + * + * Returns and empty signature and checks if the given signature is empty. + */ + struct none { + /** + * \brief Return an empty string + */ + std::string sign(const std::string& /*unused*/, std::error_code& ec) const { + ec.clear(); + return {}; + } + /** + * \brief Check if the given signature is empty. + * + * JWT's with "none" algorithm should not contain a signature. + * \param signature Signature data to verify + * \param ec error_code filled with details about the error + */ + void verify(const std::string& /*unused*/, const std::string& signature, std::error_code& ec) const { + ec.clear(); + if (!signature.empty()) { ec = error::signature_verification_error::invalid_signature; } + } + /// Get algorithm name + std::string name() const { return "none"; } + }; + /** + * \brief Base class for HMAC family of algorithms + */ + struct hmacsha { + /** + * Construct new hmac algorithm + * \param key Key to use for HMAC + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + hmacsha(std::string key, const EVP_MD* (*md)(), std::string name) + : secret(std::move(key)), md(md), alg_name(std::move(name)) {} + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return HMAC signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + std::string res(static_cast(EVP_MAX_MD_SIZE), '\0'); + auto len = static_cast(res.size()); + if (HMAC(md(), secret.data(), static_cast(secret.size()), + reinterpret_cast(data.data()), static_cast(data.size()), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &len) == nullptr) { + ec = error::signature_generation_error::hmac_failed; + return {}; + } + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details about failure. + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto res = sign(data, ec); + if (ec) return; + + bool matched = true; + for (size_t i = 0; i < std::min(res.size(), signature.size()); i++) + if (res[i] != signature[i]) matched = false; + if (res.size() != signature.size()) matched = false; + if (!matched) { + ec = error::signature_verification_error::invalid_signature; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// HMAC secrect + const std::string secret; + /// HMAC hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for RSA family of algorithms + */ + struct rsa { + /** + * Construct new rsa algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + rsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw error::rsa_exception(error::rsa_error::no_key_provided); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return RSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_SignInit(ctx.get(), md())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + std::string res(EVP_PKEY_size(pkey.get()), '\0'); + unsigned int len = 0; + + if (!EVP_SignUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_SignFinal(ctx.get(), (unsigned char*)res.data(), &len, pkey.get()) == 0) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + res.resize(len); + return res; + } + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on failure + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_VerifyInit(ctx.get(), md())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (!EVP_VerifyUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + auto res = EVP_VerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + static_cast(signature.size()), pkey.get()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL structure containing converted keys + helper::evp_pkey_handle pkey; + /// Hash generator + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + /** + * \brief Base class for ECDSA family of algorithms + */ + struct ecdsa { + /** + * Construct new ecdsa algorithm + * + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always fail + * \param public_key_password Password to decrypt public key pem + * \param private_key_password Password to decrypt private key pem + * \param md Pointer to hash function + * \param name Name of the algorithm + * \param siglen The bit length of the signature + */ + ecdsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name, size_t siglen) + : md(md), alg_name(std::move(name)), signature_length(siglen) { + if (!private_key.empty()) { + pkey = helper::load_private_ec_key_from_string(private_key, private_key_password); + check_private_key(pkey.get()); + } else if (!public_key.empty()) { + pkey = helper::load_public_ec_key_from_string(public_key, public_key_password); + check_public_key(pkey.get()); + } else { + throw error::ecdsa_exception(error::ecdsa_error::no_key_provided); + } + if (!pkey) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); + + size_t keysize = EVP_PKEY_bits(pkey.get()); + if (keysize != signature_length * 4 && (signature_length != 132 || keysize != 521)) + throw error::ecdsa_exception(error::ecdsa_error::invalid_key_size); + } + + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_DigestSignInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + + size_t len = 0; + if (!EVP_DigestSignFinal(ctx.get(), nullptr, &len)) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + std::string res(len, '\0'); + if (!EVP_DigestSignFinal(ctx.get(), (unsigned char*)res.data(), &len)) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + res.resize(len); + return der_to_p1363_signature(res, ec); + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + std::string der_signature = p1363_to_der_signature(signature, ec); + if (ec) { return; } + + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, md(), nullptr, pkey.get())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (!EVP_DigestUpdate(ctx.get(), data.data(), data.size())) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + +#if OPENSSL_VERSION_NUMBER < 0x10002000L + unsigned char* der_sig_data = reinterpret_cast(const_cast(der_signature.data())); +#else + const unsigned char* der_sig_data = reinterpret_cast(der_signature.data()); +#endif + auto res = + EVP_DigestVerifyFinal(ctx.get(), der_sig_data, static_cast(der_signature.length())); + if (res == 0) { + ec = error::signature_verification_error::invalid_signature; + return; + } + if (res == -1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + static void check_public_key(EVP_PKEY* pkey) { +#ifdef JWT_OPENSSL_3_0 + std::unique_ptr ctx( + EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free); + if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); } + if (EVP_PKEY_public_check(ctx.get()) != 1) { + throw error::ecdsa_exception(error::ecdsa_error::invalid_key); + } +#else + std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); + if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } + if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); +#endif + } + + static void check_private_key(EVP_PKEY* pkey) { +#ifdef JWT_OPENSSL_3_0 + std::unique_ptr ctx( + EVP_PKEY_CTX_new_from_pkey(nullptr, pkey, nullptr), EVP_PKEY_CTX_free); + if (!ctx) { throw error::ecdsa_exception(error::ecdsa_error::create_context_failed); } + if (EVP_PKEY_private_check(ctx.get()) != 1) { + throw error::ecdsa_exception(error::ecdsa_error::invalid_key); + } +#else + std::unique_ptr eckey(EVP_PKEY_get1_EC_KEY(pkey), EC_KEY_free); + if (!eckey) { throw error::ecdsa_exception(error::ecdsa_error::invalid_key); } + if (EC_KEY_check_key(eckey.get()) == 0) throw error::ecdsa_exception(error::ecdsa_error::invalid_key); +#endif + } + + std::string der_to_p1363_signature(const std::string& der_signature, std::error_code& ec) const { + const unsigned char* possl_signature = reinterpret_cast(der_signature.data()); + std::unique_ptr sig( + d2i_ECDSA_SIG(nullptr, &possl_signature, static_cast(der_signature.length())), + ECDSA_SIG_free); + if (!sig) { + ec = error::signature_generation_error::signature_decoding_failed; + return {}; + } + +#ifdef JWT_OPENSSL_1_0_0 + + auto rr = helper::bn2raw(sig->r); + auto rs = helper::bn2raw(sig->s); +#else + const BIGNUM* r; + const BIGNUM* s; + ECDSA_SIG_get0(sig.get(), &r, &s); + auto rr = helper::bn2raw(r); + auto rs = helper::bn2raw(s); +#endif + if (rr.size() > signature_length / 2 || rs.size() > signature_length / 2) + throw std::logic_error("bignum size exceeded expected length"); + rr.insert(0, signature_length / 2 - rr.size(), '\0'); + rs.insert(0, signature_length / 2 - rs.size(), '\0'); + return rr + rs; + } + + std::string p1363_to_der_signature(const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto r = helper::raw2bn(signature.substr(0, signature.size() / 2)); + auto s = helper::raw2bn(signature.substr(signature.size() / 2)); + + ECDSA_SIG* psig; +#ifdef JWT_OPENSSL_1_0_0 + ECDSA_SIG sig; + sig.r = r.get(); + sig.s = s.get(); + psig = &sig; +#else + std::unique_ptr sig(ECDSA_SIG_new(), ECDSA_SIG_free); + if (!sig) { + ec = error::signature_verification_error::create_context_failed; + return {}; + } + ECDSA_SIG_set0(sig.get(), r.release(), s.release()); + psig = sig.get(); +#endif + + int length = i2d_ECDSA_SIG(psig, nullptr); + if (length < 0) { + ec = error::signature_verification_error::signature_encoding_failed; + return {}; + } + std::string der_signature(length, '\0'); + unsigned char* psbuffer = (unsigned char*)der_signature.data(); + length = i2d_ECDSA_SIG(psig, &psbuffer); + if (length < 0) { + ec = error::signature_verification_error::signature_encoding_failed; + return {}; + } + der_signature.resize(length); + return der_signature; + } + + /// OpenSSL struct containing keys + helper::evp_pkey_handle pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + /// Length of the resulting signature + const size_t signature_length; + }; + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) + /** + * \brief Base class for EdDSA family of algorithms + * + * https://tools.ietf.org/html/rfc8032 + * + * The EdDSA algorithms were introduced in [OpenSSL v1.1.1](https://www.openssl.org/news/openssl-1.1.1-notes.html), + * so these algorithms are only available when building against this version or higher. + */ + struct eddsa { + /** + * Construct new eddsa algorithm + * \param public_key EdDSA public key in PEM format + * \param private_key EdDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + * \param name Name of the algorithm + */ + eddsa(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, std::string name) + : alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw error::ecdsa_exception(error::ecdsa_error::load_key_bio_read); + } + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return EdDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + if (!EVP_DigestSignInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + + size_t len = EVP_PKEY_size(pkey.get()); + std::string res(len, '\0'); + +// LibreSSL is the special kid in the block, as it does not support EVP_DigestSign. +// OpenSSL on the otherhand does not support using EVP_DigestSignUpdate for eddsa, which is why we end up with this +// mess. +#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) + ERR_clear_error(); + if (EVP_DigestSignUpdate(ctx.get(), reinterpret_cast(data.data()), data.size()) != + 1) { + std::cout << ERR_error_string(ERR_get_error(), NULL) << std::endl; + ec = error::signature_generation_error::signupdate_failed; + return {}; + } + if (EVP_DigestSignFinal(ctx.get(), reinterpret_cast(&res[0]), &len) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#else + if (EVP_DigestSign(ctx.get(), reinterpret_cast(&res[0]), &len, + reinterpret_cast(data.data()), data.size()) != 1) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } +#endif + + res.resize(len); + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with details on error + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + auto ctx = helper::make_evp_md_ctx(); + if (!ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + if (!EVP_DigestVerifyInit(ctx.get(), nullptr, nullptr, nullptr, pkey.get())) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } +// LibreSSL is the special kid in the block, as it does not support EVP_DigestVerify. +// OpenSSL on the otherhand does not support using EVP_DigestVerifyUpdate for eddsa, which is why we end up with this +// mess. +#if defined(LIBRESSL_VERSION_NUMBER) || defined(LIBWOLFSSL_VERSION_HEX) + if (EVP_DigestVerifyUpdate(ctx.get(), reinterpret_cast(data.data()), + data.size()) != 1) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + if (EVP_DigestVerifyFinal(ctx.get(), reinterpret_cast(signature.data()), + signature.size()) != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#else + auto res = EVP_DigestVerify(ctx.get(), reinterpret_cast(signature.data()), + signature.size(), reinterpret_cast(data.data()), + data.size()); + if (res != 1) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } +#endif + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL struct containing keys + helper::evp_pkey_handle pkey; + /// algorithm's name + const std::string alg_name; + }; +#endif + /** + * \brief Base class for PSS-RSA family of algorithms + */ + struct pss { + /** + * Construct new pss algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + * \param md Pointer to hash function + * \param name Name of the algorithm + */ + pss(const std::string& public_key, const std::string& private_key, const std::string& public_key_password, + const std::string& private_key_password, const EVP_MD* (*md)(), std::string name) + : md(md), alg_name(std::move(name)) { + if (!private_key.empty()) { + pkey = helper::load_private_key_from_string(private_key, private_key_password); + } else if (!public_key.empty()) { + pkey = helper::load_public_key_from_string(public_key, public_key_password); + } else + throw error::rsa_exception(error::rsa_error::no_key_provided); + } + + /** + * Sign jwt data + * \param data The data to sign + * \param ec error_code filled with details on error + * \return ECDSA signature for the given data + */ + std::string sign(const std::string& data, std::error_code& ec) const { + ec.clear(); + auto md_ctx = helper::make_evp_md_ctx(); + if (!md_ctx) { + ec = error::signature_generation_error::create_context_failed; + return {}; + } + EVP_PKEY_CTX* ctx = nullptr; + if (EVP_DigestSignInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) { + ec = error::signature_generation_error::signinit_failed; + return {}; + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { + ec = error::signature_generation_error::rsa_padding_failed; + return {}; + } +// wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior +// sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality. +#ifndef LIBWOLFSSL_VERSION_HEX + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) { + ec = error::signature_generation_error::set_rsa_pss_saltlen_failed; + return {}; + } +#endif + if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { + ec = error::signature_generation_error::digestupdate_failed; + return {}; + } + + size_t size = EVP_PKEY_size(pkey.get()); + std::string res(size, 0x00); + if (EVP_DigestSignFinal( + md_ctx.get(), + (unsigned char*)res.data(), // NOLINT(google-readability-casting) requires `const_cast` + &size) <= 0) { + ec = error::signature_generation_error::signfinal_failed; + return {}; + } + + return res; + } + + /** + * Check if signature is valid + * \param data The data to check signature against + * \param signature Signature provided by the jwt + * \param ec Filled with error details + */ + void verify(const std::string& data, const std::string& signature, std::error_code& ec) const { + ec.clear(); + + auto md_ctx = helper::make_evp_md_ctx(); + if (!md_ctx) { + ec = error::signature_verification_error::create_context_failed; + return; + } + EVP_PKEY_CTX* ctx = nullptr; + if (EVP_DigestVerifyInit(md_ctx.get(), &ctx, md(), nullptr, pkey.get()) != 1) { + ec = error::signature_verification_error::verifyinit_failed; + return; + } + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PSS_PADDING) <= 0) { + ec = error::signature_generation_error::rsa_padding_failed; + return; + } +// wolfSSL does not require EVP_PKEY_CTX_set_rsa_pss_saltlen. The default behavior +// sets the salt length to the hash length. Unlike OpenSSL which exposes this functionality. +#ifndef LIBWOLFSSL_VERSION_HEX + if (EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, -1) <= 0) { + ec = error::signature_verification_error::set_rsa_pss_saltlen_failed; + return; + } +#endif + if (EVP_DigestUpdate(md_ctx.get(), data.data(), data.size()) != 1) { + ec = error::signature_verification_error::verifyupdate_failed; + return; + } + + if (EVP_DigestVerifyFinal(md_ctx.get(), (unsigned char*)signature.data(), signature.size()) <= 0) { + ec = error::signature_verification_error::verifyfinal_failed; + return; + } + } + /** + * Returns the algorithm name provided to the constructor + * \return algorithm's name + */ + std::string name() const { return alg_name; } + + private: + /// OpenSSL structure containing keys + helper::evp_pkey_handle pkey; + /// Hash generator function + const EVP_MD* (*md)(); + /// algorithm's name + const std::string alg_name; + }; + + /** + * HS256 algorithm + */ + struct hs256 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs256(std::string key) : hmacsha(std::move(key), EVP_sha256, "HS256") {} + }; + /** + * HS384 algorithm + */ + struct hs384 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs384(std::string key) : hmacsha(std::move(key), EVP_sha384, "HS384") {} + }; + /** + * HS512 algorithm + */ + struct hs512 : public hmacsha { + /** + * Construct new instance of algorithm + * \param key HMAC signing key + */ + explicit hs512(std::string key) : hmacsha(std::move(key), EVP_sha512, "HS512") {} + }; + /** + * RS256 algorithm + */ + struct rs256 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "RS256") {} + }; + /** + * RS384 algorithm + */ + struct rs384 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "RS384") {} + }; + /** + * RS512 algorithm + */ + struct rs512 : public rsa { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit rs512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : rsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "RS512") {} + }; + /** + * ES256 algorithm + */ + struct es256 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256", 64) {} + }; + /** + * ES384 algorithm + */ + struct es384 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "ES384", 96) {} + }; + /** + * ES512 algorithm + */ + struct es512 : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit es512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "ES512", 132) {} + }; + /** + * ES256K algorithm + */ + struct es256k : public ecdsa { + /** + * Construct new instance of algorithm + * \param public_key ECDSA public key in PEM format + * \param private_key ECDSA private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit es256k(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : ecdsa(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "ES256K", 64) {} + }; + +#if !defined(JWT_OPENSSL_1_0_0) && !defined(JWT_OPENSSL_1_1_0) + /** + * Ed25519 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed25519 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed25519 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed25519 public key in PEM format + * \param private_key Ed25519 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed25519(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; + + /** + * Ed448 algorithm + * + * https://en.wikipedia.org/wiki/EdDSA#Ed448 + * + * Requires at least OpenSSL 1.1.1. + */ + struct ed448 : public eddsa { + /** + * Construct new instance of algorithm + * \param public_key Ed448 public key in PEM format + * \param private_key Ed448 private key or empty string if not available. If empty, signing will always + * fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password + * to decrypt private key pem. + */ + explicit ed448(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : eddsa(public_key, private_key, public_key_password, private_key_password, "EdDSA") {} + }; +#endif + + /** + * PS256 algorithm + */ + struct ps256 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps256(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha256, "PS256") {} + }; + /** + * PS384 algorithm + */ + struct ps384 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps384(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha384, "PS384") {} + }; + /** + * PS512 algorithm + */ + struct ps512 : public pss { + /** + * Construct new instance of algorithm + * \param public_key RSA public key in PEM format + * \param private_key RSA private key or empty string if not available. If empty, signing will always fail. + * \param public_key_password Password to decrypt public key pem. + * \param private_key_password Password to decrypt private key pem. + */ + explicit ps512(const std::string& public_key, const std::string& private_key = "", + const std::string& public_key_password = "", const std::string& private_key_password = "") + : pss(public_key, private_key, public_key_password, private_key_password, EVP_sha512, "PS512") {} + }; + } // namespace algorithm + + /** + * \brief JSON Abstractions for working with any library + */ + namespace json { + /** + * \brief Generic JSON types used in JWTs + * + * This enum is to abstract the third party underlying types + */ + enum class type { boolean, integer, number, string, array, object }; + } // namespace json + + namespace details { +#ifdef __cpp_lib_void_t + template + using void_t = std::void_t; +#else + // https://en.cppreference.com/w/cpp/types/void_t + template + struct make_void { + using type = void; + }; + + template + using void_t = typename make_void::type; +#endif + +#ifdef __cpp_lib_experimental_detect + template class _Op, typename... _Args> + using is_detected = std::experimental::is_detected<_Op, _Args...>; +#else + struct nonesuch { + nonesuch() = delete; + ~nonesuch() = delete; + nonesuch(nonesuch const&) = delete; + nonesuch(nonesuch const&&) = delete; + void operator=(nonesuch const&) = delete; + void operator=(nonesuch&&) = delete; + }; + + // https://en.cppreference.com/w/cpp/experimental/is_detected + template class Op, class... Args> + struct detector { + using value = std::false_type; + using type = Default; + }; + + template class Op, class... Args> + struct detector>, Op, Args...> { + using value = std::true_type; + using type = Op; + }; + + template class Op, class... Args> + using is_detected = typename detector::value; +#endif + + template + using is_signature = typename std::is_same; + + template class Op, typename Signature> + struct is_function_signature_detected { + using type = Op; + static constexpr auto value = is_detected::value && std::is_function::value && + is_signature::value; + }; + + template + struct supports_get_type { + template + using get_type_t = decltype(T::get_type); + + static constexpr auto value = + is_function_signature_detected::value; + + // Internal assertions for better feedback + static_assert(value, "traits implementation must provide `jwt::json::type get_type(const value_type&)`"); + }; + +#define JWT_CPP_JSON_TYPE_TYPE(TYPE) json_##TYPE_type +#define JWT_CPP_AS_TYPE_T(TYPE) as_##TYPE_t +#define JWT_CPP_SUPPORTS_AS(TYPE) \ + template \ + struct supports_as_##TYPE { \ + template \ + using JWT_CPP_AS_TYPE_T(TYPE) = decltype(T::as_##TYPE); \ + \ + static constexpr auto value = \ + is_function_signature_detected::value; \ + \ + static_assert(value, "traits implementation must provide `" #TYPE "_type as_" #TYPE "(const value_type&)`"); \ + } + + JWT_CPP_SUPPORTS_AS(object); + JWT_CPP_SUPPORTS_AS(array); + JWT_CPP_SUPPORTS_AS(string); + JWT_CPP_SUPPORTS_AS(number); + JWT_CPP_SUPPORTS_AS(integer); + JWT_CPP_SUPPORTS_AS(boolean); + +#undef JWT_CPP_JSON_TYPE_TYPE +#undef JWT_CPP_AS_TYPE_T +#undef JWT_CPP_SUPPORTS_AS + + template + struct is_valid_traits { + static constexpr auto value = + supports_get_type::value && + supports_as_object::value && + supports_as_array::value && + supports_as_string::value && + supports_as_number::value && + supports_as_integer::value && + supports_as_boolean::value; + }; + + template + struct is_valid_json_value { + static constexpr auto value = + std::is_default_constructible::value && + std::is_constructible::value && // a more generic is_copy_constructible + std::is_move_constructible::value && std::is_assignable::value && + std::is_copy_assignable::value && std::is_move_assignable::value; + // TODO(prince-chrismc): Stream operators + }; + + // https://stackoverflow.com/a/53967057/8480874 + template + struct is_iterable : std::false_type {}; + + template + struct is_iterable())), decltype(std::end(std::declval())), +#if __cplusplus > 201402L + decltype(std::cbegin(std::declval())), decltype(std::cend(std::declval())) +#else + decltype(std::begin(std::declval())), + decltype(std::end(std::declval())) +#endif + >> : std::true_type { + }; + +#if __cplusplus > 201703L + template + inline constexpr bool is_iterable_v = is_iterable::value; +#endif + + template + using is_count_signature = typename std::is_integral().count( + std::declval()))>; + + template + struct is_subcription_operator_signature : std::false_type {}; + + template + struct is_subcription_operator_signature< + object_type, string_type, + void_t().operator[](std::declval()))>> : std::true_type { + // TODO(prince-chrismc): I am not convienced this is meaningful anymore + static_assert( + value, + "object_type must implementate the subscription operator '[]' taking string_type as an argument"); + }; + + template + using is_at_const_signature = + typename std::is_same().at(std::declval())), + const value_type&>; + + template + struct is_valid_json_object { + template + using mapped_type_t = typename T::mapped_type; + template + using key_type_t = typename T::key_type; + template + using iterator_t = typename T::iterator; + template + using const_iterator_t = typename T::const_iterator; + + static constexpr auto value = + std::is_constructible::value && + is_detected::value && + std::is_same::value && + is_detected::value && + (std::is_same::value || + std::is_constructible::value) && + is_detected::value && is_detected::value && + is_iterable::value && is_count_signature::value && + is_subcription_operator_signature::value && + is_at_const_signature::value; + }; + + template + struct is_valid_json_array { + template + using value_type_t = typename T::value_type; + + static constexpr auto value = std::is_constructible::value && + is_iterable::value && + is_detected::value && + std::is_same::value; + }; + + template + using is_substr_start_end_index_signature = + typename std::is_same().substr(std::declval(), + std::declval())), + string_type>; + + template + using is_substr_start_index_signature = + typename std::is_same().substr(std::declval())), + string_type>; + + template + using is_std_operate_plus_signature = + typename std::is_same(), std::declval())), + string_type>; + + template + struct is_valid_json_string { + static constexpr auto substr = is_substr_start_end_index_signature::value && + is_substr_start_index_signature::value; + static_assert(substr, "string_type must have a substr method taking only a start index and an overload " + "taking a start and end index, both must return a string_type"); + + static constexpr auto operator_plus = is_std_operate_plus_signature::value; + static_assert(operator_plus, + "string_type must have a '+' operator implemented which returns the concatenated string"); + + static constexpr auto value = + std::is_constructible::value && substr && operator_plus; + }; + + template + struct is_valid_json_number { + static constexpr auto value = + std::is_floating_point::value && std::is_constructible::value; + }; + + template + struct is_valid_json_integer { + static constexpr auto value = std::is_signed::value && + !std::is_floating_point::value && + std::is_constructible::value; + }; + template + struct is_valid_json_boolean { + static constexpr auto value = std::is_convertible::value && + std::is_constructible::value; + }; + + template + struct is_valid_json_types { + // Internal assertions for better feedback + static_assert(is_valid_json_value::value, + "value_type must meet basic requirements, default constructor, copyable, moveable"); + static_assert(is_valid_json_object::value, + "object_type must be a string_type to value_type container"); + static_assert(is_valid_json_array::value, + "array_type must be a container of value_type"); + + static constexpr auto value = is_valid_json_value::value && + is_valid_json_object::value && + is_valid_json_array::value && + is_valid_json_string::value && + is_valid_json_number::value && + is_valid_json_integer::value && + is_valid_json_boolean::value; + }; + } // namespace details + + /** + * \brief a class to store a generic JSON value as claim + * + * \tparam json_traits : JSON implementation traits + * + * \see [RFC 7519: JSON Web Token (JWT)](https://tools.ietf.org/html/rfc7519) + */ + template + class basic_claim { + /** + * The reason behind this is to provide an expressive abstraction without + * over complexifying the API. For more information take the time to read + * https://github.com/nlohmann/json/issues/774. It maybe be expanded to + * support custom string types. + */ + static_assert(std::is_same::value || + std::is_convertible::value || + std::is_constructible::value, + "string_type must be a std::string, convertible to a std::string, or construct a std::string."); + + static_assert( + details::is_valid_json_types::value, + "must staisfy json container requirements"); + static_assert(details::is_valid_traits::value, "traits must satisfy requirements"); + + typename json_traits::value_type val; + + public: + using set_t = std::set; + + basic_claim() = default; + basic_claim(const basic_claim&) = default; + basic_claim(basic_claim&&) = default; + basic_claim& operator=(const basic_claim&) = default; + basic_claim& operator=(basic_claim&&) = default; + ~basic_claim() = default; + + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::string_type s) : val(std::move(s)) {} + JWT_CLAIM_EXPLICIT basic_claim(const date& d) + : val(typename json_traits::integer_type(std::chrono::system_clock::to_time_t(d))) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::array_type a) : val(std::move(a)) {} + JWT_CLAIM_EXPLICIT basic_claim(typename json_traits::value_type v) : val(std::move(v)) {} + JWT_CLAIM_EXPLICIT basic_claim(const set_t& s) : val(typename json_traits::array_type(s.begin(), s.end())) {} + template + basic_claim(Iterator begin, Iterator end) : val(typename json_traits::array_type(begin, end)) {} + + /** + * Get wrapped JSON value + * \return Wrapped JSON value + */ + typename json_traits::value_type to_json() const { return val; } + + /** + * Parse input stream into underlying JSON value + * \return input stream + */ + std::istream& operator>>(std::istream& is) { return is >> val; } + + /** + * Serialize claim to output stream from wrapped JSON value + * \return output stream + */ + std::ostream& operator<<(std::ostream& os) { return os << val; } + + /** + * Get type of contained JSON value + * \return Type + * \throw std::logic_error An internal error occurred + */ + json::type get_type() const { return json_traits::get_type(val); } + + /** + * Get the contained JSON value as a string + * \return content as string + * \throw std::bad_cast Content was not a string + */ + typename json_traits::string_type as_string() const { return json_traits::as_string(val); } + + /** + * \brief Get the contained JSON value as a date + * + * If the value is a decimal, it is rounded up to the closest integer + * + * \return content as date + * \throw std::bad_cast Content was not a date + */ + date as_date() const { + using std::chrono::system_clock; + if (get_type() == json::type::number) return system_clock::from_time_t(std::round(as_number())); + return system_clock::from_time_t(as_integer()); + } + + /** + * Get the contained JSON value as an array + * \return content as array + * \throw std::bad_cast Content was not an array + */ + typename json_traits::array_type as_array() const { return json_traits::as_array(val); } + + /** + * Get the contained JSON value as a set of strings + * \return content as set of strings + * \throw std::bad_cast Content was not an array of string + */ + set_t as_set() const { + set_t res; + for (const auto& e : json_traits::as_array(val)) { + res.insert(json_traits::as_string(e)); + } + return res; + } + + /** + * Get the contained JSON value as an integer + * \return content as int + * \throw std::bad_cast Content was not an int + */ + typename json_traits::integer_type as_integer() const { return json_traits::as_integer(val); } + + /** + * Get the contained JSON value as a bool + * \return content as bool + * \throw std::bad_cast Content was not a bool + */ + typename json_traits::boolean_type as_boolean() const { return json_traits::as_boolean(val); } + + /** + * Get the contained JSON value as a number + * \return content as double + * \throw std::bad_cast Content was not a number + */ + typename json_traits::number_type as_number() const { return json_traits::as_number(val); } + }; + + namespace error { + /** + * Attempt to parse JSON was unsuccessful + */ + struct invalid_json_exception : public std::runtime_error { + invalid_json_exception() : runtime_error("invalid json") {} + }; + /** + * Attempt to access claim was unsuccessful + */ + struct claim_not_present_exception : public std::out_of_range { + claim_not_present_exception() : out_of_range("claim not found") {} + }; + } // namespace error + + namespace details { + template + struct map_of_claims { + typename json_traits::object_type claims; + using basic_claim_t = basic_claim; + using iterator = typename json_traits::object_type::iterator; + using const_iterator = typename json_traits::object_type::const_iterator; + + map_of_claims() = default; + map_of_claims(const map_of_claims&) = default; + map_of_claims(map_of_claims&&) = default; + map_of_claims& operator=(const map_of_claims&) = default; + map_of_claims& operator=(map_of_claims&&) = default; + + map_of_claims(typename json_traits::object_type json) : claims(std::move(json)) {} + + iterator begin() { return claims.begin(); } + iterator end() { return claims.end(); } + const_iterator cbegin() const { return claims.begin(); } + const_iterator cend() const { return claims.end(); } + const_iterator begin() const { return claims.begin(); } + const_iterator end() const { return claims.end(); } + + /** + * \brief Parse a JSON string into a map of claims + * + * The implication is that a "map of claims" is identic to a JSON object + * + * \param str JSON data to be parse as an object + * \return content as JSON object + */ + static typename json_traits::object_type parse_claims(const typename json_traits::string_type& str) { + typename json_traits::value_type val; + if (!json_traits::parse(val, str)) throw error::invalid_json_exception(); + + return json_traits::as_object(val); + }; + + /** + * Check if a claim is present in the map + * \return true if claim was present, false otherwise + */ + bool has_claim(const typename json_traits::string_type& name) const noexcept { + return claims.count(name) != 0; + } + + /** + * Get a claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_claim(const typename json_traits::string_type& name) const { + if (!has_claim(name)) throw error::claim_not_present_exception(); + return basic_claim_t{claims.at(name)}; + } + }; + } // namespace details + + /** + * Base class that represents a token payload. + * Contains Convenience accessors for common claims. + */ + template + class payload { + protected: + details::map_of_claims payload_claims; + + public: + using basic_claim_t = basic_claim; + + /** + * Check if issuer is present ("iss") + * \return true if present, false otherwise + */ + bool has_issuer() const noexcept { return has_payload_claim("iss"); } + /** + * Check if subject is present ("sub") + * \return true if present, false otherwise + */ + bool has_subject() const noexcept { return has_payload_claim("sub"); } + /** + * Check if audience is present ("aud") + * \return true if present, false otherwise + */ + bool has_audience() const noexcept { return has_payload_claim("aud"); } + /** + * Check if expires is present ("exp") + * \return true if present, false otherwise + */ + bool has_expires_at() const noexcept { return has_payload_claim("exp"); } + /** + * Check if not before is present ("nbf") + * \return true if present, false otherwise + */ + bool has_not_before() const noexcept { return has_payload_claim("nbf"); } + /** + * Check if issued at is present ("iat") + * \return true if present, false otherwise + */ + bool has_issued_at() const noexcept { return has_payload_claim("iat"); } + /** + * Check if token id is present ("jti") + * \return true if present, false otherwise + */ + bool has_id() const noexcept { return has_payload_claim("jti"); } + /** + * Get issuer claim + * \return issuer as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_issuer() const { return get_payload_claim("iss").as_string(); } + /** + * Get subject claim + * \return subject as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_subject() const { return get_payload_claim("sub").as_string(); } + /** + * Get audience claim + * \return audience as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a set (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_audience() const { + auto aud = get_payload_claim("aud"); + if (aud.get_type() == json::type::string) return {aud.as_string()}; + + return aud.as_set(); + } + /** + * Get expires claim + * \return expires as a date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_expires_at() const { return get_payload_claim("exp").as_date(); } + /** + * Get not valid before claim + * \return nbf date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_not_before() const { return get_payload_claim("nbf").as_date(); } + /** + * Get issued at claim + * \return issued at as date in utc + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a date (Should not happen in a valid token) + */ + date get_issued_at() const { return get_payload_claim("iat").as_date(); } + /** + * Get id claim + * \return id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_id() const { return get_payload_claim("jti").as_string(); } + /** + * Check if a payload claim is present + * \return true if claim was present, false otherwise + */ + bool has_payload_claim(const typename json_traits::string_type& name) const noexcept { + return payload_claims.has_claim(name); + } + /** + * Get payload claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return payload_claims.get_claim(name); + } + }; + + /** + * Base class that represents a token header. + * Contains Convenience accessors for common claims. + */ + template + class header { + protected: + details::map_of_claims header_claims; + + public: + using basic_claim_t = basic_claim; + /** + * Check if algorithm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_header_claim("alg"); } + /** + * Check if type is present ("typ") + * \return true if present, false otherwise + */ + bool has_type() const noexcept { return has_header_claim("typ"); } + /** + * Check if content type is present ("cty") + * \return true if present, false otherwise + */ + bool has_content_type() const noexcept { return has_header_claim("cty"); } + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_header_claim("kid"); } + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_header_claim("alg").as_string(); } + /** + * Get type claim + * \return type as a string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_type() const { return get_header_claim("typ").as_string(); } + /** + * Get content type claim + * \return content type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_content_type() const { return get_header_claim("cty").as_string(); } + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_header_claim("kid").as_string(); } + /** + * Check if a header claim is present + * \return true if claim was present, false otherwise + */ + bool has_header_claim(const typename json_traits::string_type& name) const noexcept { + return header_claims.has_claim(name); + } + /** + * Get header claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return header_claims.get_claim(name); + } + }; + + /** + * Class containing all information about a decoded token + */ + template + class decoded_jwt : public header, public payload { + protected: + /// Unmodified token, as passed to constructor + typename json_traits::string_type token; + /// Header part decoded from base64 + typename json_traits::string_type header; + /// Unmodified header part in base64 + typename json_traits::string_type header_base64; + /// Payload part decoded from base64 + typename json_traits::string_type payload; + /// Unmodified payload part in base64 + typename json_traits::string_type payload_base64; + /// Signature part decoded from base64 + typename json_traits::string_type signature; + /// Unmodified signature part in base64 + typename json_traits::string_type signature_base64; + + public: + using basic_claim_t = basic_claim; +#ifndef JWT_DISABLE_BASE64 + /** + * \brief Parses a given token + * + * \note Decodes using the jwt::base64url which supports an std::string + * + * \param token The token to parse + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + JWT_CLAIM_EXPLICIT decoded_jwt(const typename json_traits::string_type& token) + : decoded_jwt(token, [](const typename json_traits::string_type& str) { + return base::decode(base::pad(str)); + }) {} +#endif + /** + * \brief Parses a given token + * + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token The token to parse + * \param decode The function to decode the token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt(const typename json_traits::string_type& token, Decode decode) : token(token) { + auto hdr_end = token.find('.'); + if (hdr_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + auto payload_end = token.find('.', hdr_end + 1); + if (payload_end == json_traits::string_type::npos) throw std::invalid_argument("invalid token supplied"); + header_base64 = token.substr(0, hdr_end); + payload_base64 = token.substr(hdr_end + 1, payload_end - hdr_end - 1); + signature_base64 = token.substr(payload_end + 1); + + header = decode(header_base64); + payload = decode(payload_base64); + signature = decode(signature_base64); + + this->header_claims = details::map_of_claims::parse_claims(header); + this->payload_claims = details::map_of_claims::parse_claims(payload); + } + + /** + * Get token string, as passed to constructor + * \return token as passed to constructor + */ + const typename json_traits::string_type& get_token() const noexcept { return token; } + /** + * Get header part as json string + * \return header part after base64 decoding + */ + const typename json_traits::string_type& get_header() const noexcept { return header; } + /** + * Get payload part as json string + * \return payload part after base64 decoding + */ + const typename json_traits::string_type& get_payload() const noexcept { return payload; } + /** + * Get signature part as json string + * \return signature part after base64 decoding + */ + const typename json_traits::string_type& get_signature() const noexcept { return signature; } + /** + * Get header part as base64 string + * \return header part before base64 decoding + */ + const typename json_traits::string_type& get_header_base64() const noexcept { return header_base64; } + /** + * Get payload part as base64 string + * \return payload part before base64 decoding + */ + const typename json_traits::string_type& get_payload_base64() const noexcept { return payload_base64; } + /** + * Get signature part as base64 string + * \return signature part before base64 decoding + */ + const typename json_traits::string_type& get_signature_base64() const noexcept { return signature_base64; } + /** + * Get all payload as JSON object + * \return map of claims + */ + typename json_traits::object_type get_payload_json() const { return this->payload_claims.claims; } + /** + * Get all header as JSON object + * \return map of claims + */ + typename json_traits::object_type get_header_json() const { return this->header_claims.claims; } + /** + * Get a payload claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_payload_claim(const typename json_traits::string_type& name) const { + return this->payload_claims.get_claim(name); + } + /** + * Get a header claim by name + * + * \param name the name of the desired claim + * \return Requested claim + * \throw jwt::error::claim_not_present_exception if the claim was not present + */ + basic_claim_t get_header_claim(const typename json_traits::string_type& name) const { + return this->header_claims.get_claim(name); + } + }; + + /** + * Builder class to build and sign a new token + * Use jwt::create() to get an instance of this class. + */ + template + class builder { + typename json_traits::object_type header_claims; + typename json_traits::object_type payload_claims; + + public: + builder() = default; + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + header_claims[id] = std::move(c); + return *this; + } + + /** + * Set a header claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_header_claim(const typename json_traits::string_type& id, basic_claim c) { + header_claims[id] = c.to_json(); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, typename json_traits::value_type c) { + payload_claims[id] = std::move(c); + return *this; + } + /** + * Set a payload claim. + * \param id Name of the claim + * \param c Claim to add + * \return *this to allow for method chaining + */ + builder& set_payload_claim(const typename json_traits::string_type& id, basic_claim c) { + payload_claims[id] = c.to_json(); + return *this; + } + /** + * \brief Set algorithm claim + * You normally don't need to do this, as the algorithm is automatically set if you don't change it. + * + * \param str Name of algorithm + * \return *this to allow for method chaining + */ + builder& set_algorithm(typename json_traits::string_type str) { + return set_header_claim("alg", typename json_traits::value_type(str)); + } + /** + * Set type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_type(typename json_traits::string_type str) { + return set_header_claim("typ", typename json_traits::value_type(str)); + } + /** + * Set content type claim + * \param str Type to set + * \return *this to allow for method chaining + */ + builder& set_content_type(typename json_traits::string_type str) { + return set_header_claim("cty", typename json_traits::value_type(str)); + } + /** + * \brief Set key id claim + * + * \param str Key id to set + * \return *this to allow for method chaining + */ + builder& set_key_id(typename json_traits::string_type str) { + return set_header_claim("kid", typename json_traits::value_type(str)); + } + /** + * Set issuer claim + * \param str Issuer to set + * \return *this to allow for method chaining + */ + builder& set_issuer(typename json_traits::string_type str) { + return set_payload_claim("iss", typename json_traits::value_type(str)); + } + /** + * Set subject claim + * \param str Subject to set + * \return *this to allow for method chaining + */ + builder& set_subject(typename json_traits::string_type str) { + return set_payload_claim("sub", typename json_traits::value_type(str)); + } + /** + * Set audience claim + * \param a Audience set + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::array_type a) { + return set_payload_claim("aud", typename json_traits::value_type(a)); + } + /** + * Set audience claim + * \param aud Single audience + * \return *this to allow for method chaining + */ + builder& set_audience(typename json_traits::string_type aud) { + return set_payload_claim("aud", typename json_traits::value_type(aud)); + } + /** + * Set expires at claim + * \param d Expires time + * \return *this to allow for method chaining + */ + builder& set_expires_at(const date& d) { return set_payload_claim("exp", basic_claim(d)); } + /** + * Set not before claim + * \param d First valid time + * \return *this to allow for method chaining + */ + builder& set_not_before(const date& d) { return set_payload_claim("nbf", basic_claim(d)); } + /** + * Set issued at claim + * \param d Issued at time, should be current time + * \return *this to allow for method chaining + */ + builder& set_issued_at(const date& d) { return set_payload_claim("iat", basic_claim(d)); } + /** + * Set id claim + * \param str ID to set + * \return *this to allow for method chaining + */ + builder& set_id(const typename json_traits::string_type& str) { + return set_payload_claim("jti", typename json_traits::value_type(str)); + } + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode) const { + std::error_code ec; + auto res = sign(algo, encode, ec); + error::throw_if_error(ec); + return res; + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo) const { + std::error_code ec; + auto res = sign(algo, ec); + error::throw_if_error(ec); + return res; + } +#endif + + /** + * Sign token and return result + * \tparam Algo Callable method which takes a string_type and return the signed input as a string_type + * \tparam Encode Callable method which takes a string_type and base64url safe encodes it, + * MUST return the result with no padding; trim the result. + * \param algo Instance of an algorithm to sign the token with + * \param encode Callable to transform the serialized json to base64 with no padding + * \param ec error_code filled with details on error + * \return Final token as a string + * + * \note If the 'alg' header in not set in the token it will be set to `algo.name()` + */ + template + typename json_traits::string_type sign(const Algo& algo, Encode encode, std::error_code& ec) const { + // make a copy such that a builder can be re-used + typename json_traits::object_type obj_header = header_claims; + if (header_claims.count("alg") == 0) obj_header["alg"] = typename json_traits::value_type(algo.name()); + + const auto header = encode(json_traits::serialize(typename json_traits::value_type(obj_header))); + const auto payload = encode(json_traits::serialize(typename json_traits::value_type(payload_claims))); + const auto token = header + "." + payload; + + auto signature = algo.sign(token, ec); + if (ec) return {}; + + return token + "." + encode(signature); + } +#ifndef JWT_DISABLE_BASE64 + /** + * Sign token and return result + * + * using the `jwt::base` functions provided + * + * \param algo Instance of an algorithm to sign the token with + * \param ec error_code filled with details on error + * \return Final token as a string + */ + template + typename json_traits::string_type sign(const Algo& algo, std::error_code& ec) const { + return sign( + algo, + [](const typename json_traits::string_type& data) { + return base::trim(base::encode(data)); + }, + ec); + } +#endif + }; + + namespace verify_ops { + /** + * This is the base container which holds the token that need to be verified + */ + template + struct verify_context { + verify_context(date ctime, const decoded_jwt& j, size_t l) + : current_time(ctime), jwt(j), default_leeway(l) {} + // Current time, retrieved from the verifiers clock and cached for performance and consistency + date current_time; + // The jwt passed to the verifier + const decoded_jwt& jwt; + // The configured default leeway for this verification + size_t default_leeway{0}; + + // The claim key to apply this comparison on + typename json_traits::string_type claim_key{}; + + // Helper method to get a claim from the jwt in this context + basic_claim get_claim(bool in_header, std::error_code& ec) const { + if (in_header) { + if (!jwt.has_header_claim(claim_key)) { + ec = error::token_verification_error::missing_claim; + return {}; + } + return jwt.get_header_claim(claim_key); + } else { + if (!jwt.has_payload_claim(claim_key)) { + ec = error::token_verification_error::missing_claim; + return {}; + } + return jwt.get_payload_claim(claim_key); + } + } + basic_claim get_claim(bool in_header, json::type t, std::error_code& ec) const { + auto c = get_claim(in_header, ec); + if (ec) return {}; + if (c.get_type() != t) { + ec = error::token_verification_error::claim_type_missmatch; + return {}; + } + return c; + } + basic_claim get_claim(std::error_code& ec) const { return get_claim(false, ec); } + basic_claim get_claim(json::type t, std::error_code& ec) const { + return get_claim(false, t, ec); + } + }; + + /** + * This is the default operation and does case sensitive matching + */ + template + struct equals_claim { + const basic_claim expected; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto jc = ctx.get_claim(in_header, expected.get_type(), ec); + if (ec) return; + const bool matches = [&]() { + switch (expected.get_type()) { + case json::type::boolean: return expected.as_boolean() == jc.as_boolean(); + case json::type::integer: return expected.as_integer() == jc.as_integer(); + case json::type::number: return expected.as_number() == jc.as_number(); + case json::type::string: return expected.as_string() == jc.as_string(); + case json::type::array: + case json::type::object: + return json_traits::serialize(expected.to_json()) == json_traits::serialize(jc.to_json()); + default: throw std::logic_error("internal error, should be unreachable"); + } + }(); + if (!matches) { + ec = error::token_verification_error::claim_value_missmatch; + return; + } + } + }; + + /** + * Checks that the current time is before the time specified in the given + * claim. This is identical to how the "exp" check works. + */ + template + struct date_before_claim { + const size_t leeway; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto jc = ctx.get_claim(in_header, json::type::integer, ec); + if (ec) return; + auto c = jc.as_date(); + if (ctx.current_time > c + std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + } + } + }; + + /** + * Checks that the current time is after the time specified in the given + * claim. This is identical to how the "nbf" and "iat" check works. + */ + template + struct date_after_claim { + const size_t leeway; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto jc = ctx.get_claim(in_header, json::type::integer, ec); + if (ec) return; + auto c = jc.as_date(); + if (ctx.current_time < c - std::chrono::seconds(leeway)) { + ec = error::token_verification_error::token_expired; + } + } + }; + + /** + * Checks if the given set is a subset of the set inside the token. + * If the token value is a string it is traited as a set of a single element. + * The comparison is case sensitive. + */ + template + struct is_subset_claim { + const typename basic_claim::set_t expected; + void operator()(const verify_context& ctx, std::error_code& ec) const { + auto c = ctx.get_claim(in_header, ec); + if (ec) return; + if (c.get_type() == json::type::string) { + if (expected.size() != 1 || *expected.begin() != c.as_string()) { + ec = error::token_verification_error::audience_missmatch; + return; + } + } else if (c.get_type() == json::type::array) { + auto jc = c.as_set(); + for (auto& e : expected) { + if (jc.find(e) == jc.end()) { + ec = error::token_verification_error::audience_missmatch; + return; + } + } + } else { + ec = error::token_verification_error::claim_type_missmatch; + return; + } + } + }; + + /** + * Checks if the claim is a string and does an case insensitive comparison. + */ + template + struct insensitive_string_claim { + const typename json_traits::string_type expected; + std::locale locale; + insensitive_string_claim(const typename json_traits::string_type& e, std::locale loc) + : expected(to_lower_unicode(e, loc)), locale(loc) {} + + void operator()(const verify_context& ctx, std::error_code& ec) const { + const auto c = ctx.get_claim(in_header, json::type::string, ec); + if (ec) return; + if (to_lower_unicode(c.as_string(), locale) != expected) { + ec = error::token_verification_error::claim_value_missmatch; + } + } + + static std::string to_lower_unicode(const std::string& str, const std::locale& loc) { + std::mbstate_t state = std::mbstate_t(); + const char* in_next = str.data(); + const char* in_end = str.data() + str.size(); + std::wstring wide; + wide.reserve(str.size()); + + while (in_next != in_end) { + wchar_t wc; + std::size_t result = std::mbrtowc(&wc, in_next, in_end - in_next, &state); + if (result == static_cast(-1)) { + throw std::runtime_error("encoding error: " + std::string(std::strerror(errno))); + } else if (result == static_cast(-2)) { + throw std::runtime_error("conversion error: next bytes constitute an incomplete, but so far " + "valid, multibyte character."); + } + in_next += result; + wide.push_back(wc); + } + + auto& f = std::use_facet>(loc); + f.tolower(&wide[0], &wide[0] + wide.size()); + + std::string out; + out.reserve(wide.size()); + for (wchar_t wc : wide) { + char mb[MB_LEN_MAX]; + std::size_t n = std::wcrtomb(mb, wc, &state); + if (n != static_cast(-1)) out.append(mb, n); + } + + return out; + } + }; + } // namespace verify_ops + + /** + * Verifier class used to check if a decoded token contains all claims required by your application and has a valid + * signature. + */ + template + class verifier { + public: + using basic_claim_t = basic_claim; + /** + * Verification function + * + * This gets passed the current verifier, a reference to the decoded jwt, a reference to the key of this claim, + * as well as a reference to an error_code. + * The function checks if the actual value matches certain rules (e.g. equality to value x) and sets the error_code if + * it does not. Once a non zero error_code is encountered the verification stops and this error_code becomes the result + * returned from verify + */ + using verify_check_fn_t = + std::function&, std::error_code& ec)>; + + private: + struct algo_base { + virtual ~algo_base() = default; + virtual void verify(const std::string& data, const std::string& sig, std::error_code& ec) = 0; + }; + template + struct algo : public algo_base { + T alg; + explicit algo(T a) : alg(a) {} + void verify(const std::string& data, const std::string& sig, std::error_code& ec) override { + alg.verify(data, sig, ec); + } + }; + /// Required claims + std::unordered_map claims; + /// Leeway time for exp, nbf and iat + size_t default_leeway = 0; + /// Instance of clock type + Clock clock; + /// Supported algorithms + std::unordered_map> algs; + + public: + /** + * Constructor for building a new verifier instance + * \param c Clock instance + */ + explicit verifier(Clock c) : clock(c) { + claims["exp"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { + if (!ctx.jwt.has_expires_at()) return; + auto exp = ctx.jwt.get_expires_at(); + if (ctx.current_time > exp + std::chrono::seconds(ctx.default_leeway)) { + ec = error::token_verification_error::token_expired; + } + }; + claims["iat"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { + if (!ctx.jwt.has_issued_at()) return; + auto iat = ctx.jwt.get_issued_at(); + if (ctx.current_time < iat - std::chrono::seconds(ctx.default_leeway)) { + ec = error::token_verification_error::token_expired; + } + }; + claims["nbf"] = [](const verify_ops::verify_context& ctx, std::error_code& ec) { + if (!ctx.jwt.has_not_before()) return; + auto nbf = ctx.jwt.get_not_before(); + if (ctx.current_time < nbf - std::chrono::seconds(ctx.default_leeway)) { + ec = error::token_verification_error::token_expired; + } + }; + } + + /** + * Set default leeway to use. + * \param leeway Default leeway to use if not specified otherwise + * \return *this to allow chaining + */ + verifier& leeway(size_t leeway) { + default_leeway = leeway; + return *this; + } + /** + * Set leeway for expires at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for expires at. + * \return *this to allow chaining + */ + verifier& expires_at_leeway(size_t leeway) { + claims["exp"] = verify_ops::date_before_claim{leeway}; + return *this; + } + /** + * Set leeway for not before. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for not before. + * \return *this to allow chaining + */ + verifier& not_before_leeway(size_t leeway) { + claims["nbf"] = verify_ops::date_after_claim{leeway}; + return *this; + } + /** + * Set leeway for issued at. + * If not specified the default leeway will be used. + * \param leeway Set leeway to use for issued at. + * \return *this to allow chaining + */ + verifier& issued_at_leeway(size_t leeway) { + claims["iat"] = verify_ops::date_after_claim{leeway}; + return *this; + } + + /** + * Set an type to check for. + * + * According to [RFC 7519 Section 5.1](https://datatracker.ietf.org/doc/html/rfc7519#section-5.1), + * This parameter is ignored by JWT implementations; any processing of this parameter is performed by the JWT application. + * Check is casesensitive. + * + * \param type Type Header Parameter to check for. + * \param locale Localization functionality to use when comparing + * \return *this to allow chaining + */ + verifier& with_type(const typename json_traits::string_type& type, std::locale locale = std::locale{}) { + return with_claim("typ", verify_ops::insensitive_string_claim{type, std::move(locale)}); + } + + /** + * Set an issuer to check for. + * Check is casesensitive. + * \param iss Issuer to check for. + * \return *this to allow chaining + */ + verifier& with_issuer(const typename json_traits::string_type& iss) { + return with_claim("iss", basic_claim_t(iss)); + } + + /** + * Set a subject to check for. + * Check is casesensitive. + * \param sub Subject to check for. + * \return *this to allow chaining + */ + verifier& with_subject(const typename json_traits::string_type& sub) { + return with_claim("sub", basic_claim_t(sub)); + } + /** + * Set an audience to check for. + * If any of the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename basic_claim_t::set_t& aud) { + claims["aud"] = verify_ops::is_subset_claim{aud}; + return *this; + } + /** + * Set an audience to check for. + * If the specified audiences is not present in the token the check fails. + * \param aud Audience to check for. + * \return *this to allow chaining + */ + verifier& with_audience(const typename json_traits::string_type& aud) { + typename basic_claim_t::set_t s; + s.insert(aud); + return with_audience(s); + } + /** + * Set an id to check for. + * Check is casesensitive. + * \param id ID to check for. + * \return *this to allow chaining + */ + verifier& with_id(const typename json_traits::string_type& id) { return with_claim("jti", basic_claim_t(id)); } + + /** + * Specify a claim to check for using the specified operation. + * \param name Name of the claim to check for + * \param fn Function to use for verifying the claim + * \return *this to allow chaining + */ + verifier& with_claim(const typename json_traits::string_type& name, verify_check_fn_t fn) { + claims[name] = fn; + return *this; + } + + /** + * Specify a claim to check for equality (both type & value). + * \param name Name of the claim to check for + * \param c Claim to check for + * \return *this to allow chaining + */ + verifier& with_claim(const typename json_traits::string_type& name, basic_claim_t c) { + return with_claim(name, verify_ops::equals_claim{c}); + } + + /** + * Add an algorithm available for checking. + * \param alg Algorithm to allow + * \return *this to allow chaining + */ + template + verifier& allow_algorithm(Algorithm alg) { + algs[alg.name()] = std::make_shared>(alg); + return *this; + } + + /** + * Verify the given token. + * \param jwt Token to check + * \throw token_verification_exception Verification failed + */ + void verify(const decoded_jwt& jwt) const { + std::error_code ec; + verify(jwt, ec); + error::throw_if_error(ec); + } + /** + * Verify the given token. + * \param jwt Token to check + * \param ec error_code filled with details on error + */ + void verify(const decoded_jwt& jwt, std::error_code& ec) const { + ec.clear(); + const typename json_traits::string_type data = jwt.get_header_base64() + "." + jwt.get_payload_base64(); + const typename json_traits::string_type sig = jwt.get_signature(); + const std::string algo = jwt.get_algorithm(); + if (algs.count(algo) == 0) { + ec = error::token_verification_error::wrong_algorithm; + return; + } + algs.at(algo)->verify(data, sig, ec); + if (ec) return; + + verify_ops::verify_context ctx{clock.now(), jwt, default_leeway}; + for (auto& c : claims) { + ctx.claim_key = c.first; + c.second(ctx, ec); + if (ec) return; + } + } + }; + + /** + * \brief JSON Web Key + * + * https://tools.ietf.org/html/rfc7517 + * + * A JSON object that represents a cryptographic key. The members of + * the object represent properties of the key, including its value. + */ + template + class jwk { + using basic_claim_t = basic_claim; + const details::map_of_claims jwk_claims; + + public: + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::string_type& str) + : jwk_claims(details::map_of_claims::parse_claims(str)) {} + + JWT_CLAIM_EXPLICIT jwk(const typename json_traits::value_type& json) + : jwk_claims(json_traits::as_object(json)) {} + + /** + * Get key type claim + * + * This returns the general type (e.g. RSA or EC), not a specific algorithm value. + * \return key type as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_type() const { return get_jwk_claim("kty").as_string(); } + + /** + * Get public key usage claim + * \return usage parameter as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_use() const { return get_jwk_claim("use").as_string(); } + + /** + * Get key operation types claim + * \return key operation types as a set of strings + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename basic_claim_t::set_t get_key_operations() const { return get_jwk_claim("key_ops").as_set(); } + + /** + * Get algorithm claim + * \return algorithm as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_algorithm() const { return get_jwk_claim("alg").as_string(); } + + /** + * Get key id claim + * \return key id as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_key_id() const { return get_jwk_claim("kid").as_string(); } + + /** + * \brief Get curve claim + * + * https://www.rfc-editor.org/rfc/rfc7518.html#section-6.2.1.1 + * https://www.iana.org/assignments/jose/jose.xhtml#table-web-key-elliptic-curve + * + * \return curve as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_curve() const { return get_jwk_claim("crv").as_string(); } + + /** + * Get x5c claim + * \return x5c as an array + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a array (Should not happen in a valid token) + */ + typename json_traits::array_type get_x5c() const { return get_jwk_claim("x5c").as_array(); }; + + /** + * Get X509 URL claim + * \return x5u as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5u() const { return get_jwk_claim("x5u").as_string(); }; + + /** + * Get X509 thumbprint claim + * \return x5t as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5t() const { return get_jwk_claim("x5t").as_string(); }; + + /** + * Get X509 SHA256 thumbprint claim + * \return x5t#S256 as string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5t_sha256() const { return get_jwk_claim("x5t#S256").as_string(); }; + + /** + * Get x5c claim as a string + * \return x5c as an string + * \throw std::runtime_error If claim was not present + * \throw std::bad_cast Claim was present but not a string (Should not happen in a valid token) + */ + typename json_traits::string_type get_x5c_key_value() const { + auto x5c_array = get_jwk_claim("x5c").as_array(); + if (x5c_array.size() == 0) throw error::claim_not_present_exception(); + + return json_traits::as_string(x5c_array.front()); + }; + + /** + * Check if a key type is present ("kty") + * \return true if present, false otherwise + */ + bool has_key_type() const noexcept { return has_jwk_claim("kty"); } + + /** + * Check if a public key usage indication is present ("use") + * \return true if present, false otherwise + */ + bool has_use() const noexcept { return has_jwk_claim("use"); } + + /** + * Check if a key operations parameter is present ("key_ops") + * \return true if present, false otherwise + */ + bool has_key_operations() const noexcept { return has_jwk_claim("key_ops"); } + + /** + * Check if algorithm is present ("alg") + * \return true if present, false otherwise + */ + bool has_algorithm() const noexcept { return has_jwk_claim("alg"); } + + /** + * Check if curve is present ("crv") + * \return true if present, false otherwise + */ + bool has_curve() const noexcept { return has_jwk_claim("crv"); } + + /** + * Check if key id is present ("kid") + * \return true if present, false otherwise + */ + bool has_key_id() const noexcept { return has_jwk_claim("kid"); } + + /** + * Check if X509 URL is present ("x5u") + * \return true if present, false otherwise + */ + bool has_x5u() const noexcept { return has_jwk_claim("x5u"); } + + /** + * Check if X509 Chain is present ("x5c") + * \return true if present, false otherwise + */ + bool has_x5c() const noexcept { return has_jwk_claim("x5c"); } + + /** + * Check if a X509 thumbprint is present ("x5t") + * \return true if present, false otherwise + */ + bool has_x5t() const noexcept { return has_jwk_claim("x5t"); } + + /** + * Check if a X509 SHA256 thumbprint is present ("x5t#S256") + * \return true if present, false otherwise + */ + bool has_x5t_sha256() const noexcept { return has_jwk_claim("x5t#S256"); } + + /** + * Check if a jwks claim is present + * \return true if claim was present, false otherwise + */ + bool has_jwk_claim(const typename json_traits::string_type& name) const noexcept { + return jwk_claims.has_claim(name); + } + + /** + * Get jwks claim + * \return Requested claim + * \throw std::runtime_error If claim was not present + */ + basic_claim_t get_jwk_claim(const typename json_traits::string_type& name) const { + return jwk_claims.get_claim(name); + } + + bool empty() const noexcept { return jwk_claims.empty(); } + + /** + * Get all jwk claims + * \return Map of claims + */ + typename json_traits::object_type get_claims() const { return this->jwk_claims.claims; } + }; + + /** + * \brief JWK Set + * + * https://tools.ietf.org/html/rfc7517 + * + * A JSON object that represents a set of JWKs. The JSON object MUST + * have a "keys" member, which is an array of JWKs. + * + * This container takes a JWKs and simplifies it to a vector of JWKs + */ + template + class jwks { + public: + using jwk_t = jwk; + using jwt_vector_t = std::vector; + using iterator = typename jwt_vector_t::iterator; + using const_iterator = typename jwt_vector_t::const_iterator; + + JWT_CLAIM_EXPLICIT jwks(const typename json_traits::string_type& str) { + typename json_traits::value_type parsed_val; + if (!json_traits::parse(parsed_val, str)) throw error::invalid_json_exception(); + + const details::map_of_claims jwks_json = json_traits::as_object(parsed_val); + if (!jwks_json.has_claim("keys")) throw error::invalid_json_exception(); + + auto jwk_list = jwks_json.get_claim("keys").as_array(); + std::transform(jwk_list.begin(), jwk_list.end(), std::back_inserter(jwk_claims), + [](const typename json_traits::value_type& val) { return jwk_t{val}; }); + } + + iterator begin() { return jwk_claims.begin(); } + iterator end() { return jwk_claims.end(); } + const_iterator cbegin() const { return jwk_claims.begin(); } + const_iterator cend() const { return jwk_claims.end(); } + const_iterator begin() const { return jwk_claims.begin(); } + const_iterator end() const { return jwk_claims.end(); } + + /** + * Check if a jwk with the kid is present + * \return true if jwk was present, false otherwise + */ + bool has_jwk(const typename json_traits::string_type& key_id) const noexcept { + return find_by_kid(key_id) != end(); + } + + /** + * Get jwk + * \return Requested jwk by key_id + * \throw std::runtime_error If jwk was not present + */ + jwk_t get_jwk(const typename json_traits::string_type& key_id) const { + const auto maybe = find_by_kid(key_id); + if (maybe == end()) throw error::claim_not_present_exception(); + return *maybe; + } + + private: + jwt_vector_t jwk_claims; + + const_iterator find_by_kid(const typename json_traits::string_type& key_id) const noexcept { + return std::find_if(cbegin(), cend(), [key_id](const jwk_t& jwk) { + if (!jwk.has_key_id()) { return false; } + return jwk.get_key_id() == key_id; + }); + } + }; + + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(Clock c) { + return verifier(c); + } + + /** + * Default clock class using std::chrono::system_clock as a backend. + */ + struct default_clock { + date now() const { return date::clock::now(); } + }; + + /** + * Create a verifier using the given clock + * \param c Clock instance to use + * \return verifier instance + */ + template + verifier verify(default_clock c = {}) { + return verifier(c); + } + + /** + * Return a builder instance to create a new token + */ + template + builder create() { + return builder(); + } + + /** + * Decode a token + * \param token Token to decode + * \param decode function that will pad and base64url decode the token + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const typename json_traits::string_type& token) { + return decoded_jwt(token); + } + + template + jwk parse_jwk(const typename json_traits::string_type& token) { + return jwk(token); + } + + template + jwks parse_jwks(const typename json_traits::string_type& token) { + return jwks(token); + } +} // namespace jwt + +template +std::istream& operator>>(std::istream& is, jwt::basic_claim& c) { + return c.operator>>(is); +} + +template +std::ostream& operator<<(std::ostream& os, const jwt::basic_claim& c) { + return os << c.to_json(); +} + +#ifndef JWT_DISABLE_PICOJSON +#include "traits/kazuho-picojson/defaults.h" +#endif + +#endif diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/defaults.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/defaults.h new file mode 100644 index 00000000..affeffe8 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/defaults.h @@ -0,0 +1,88 @@ +#ifndef JWT_CPP_BOOST_JSON_DEFAULTS_H +#define JWT_CPP_BOOST_JSON_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [Boost.JSON](https://github.com/boostorg/json) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk parse_jwk(const traits::boost_json::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks parse_jwks(const traits::boost_json::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_BOOST_JSON_DEFAULTS_H diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/traits.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/traits.h new file mode 100644 index 00000000..3b27d5f2 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/boost-json/traits.h @@ -0,0 +1,80 @@ +#ifndef JWT_CPP_BOOSTJSON_TRAITS_H +#define JWT_CPP_BOOSTJSON_TRAITS_H + +#define JWT_DISABLE_PICOJSON +#include "jwt-cpp/jwt.h" + +#include +// if not boost JSON standalone then error... + +namespace jwt { + namespace traits { + namespace json = boost::json; + struct boost_json { + using value_type = json::value; + using object_type = json::object; + using array_type = json::array; + using string_type = std::string; + using number_type = double; + using integer_type = std::int64_t; + using boolean_type = bool; + + static jwt::json::type get_type(const value_type& val) { + using jwt::json::type; + + if (val.kind() == json::kind::bool_) return type::boolean; + if (val.kind() == json::kind::int64) return type::integer; + if (val.kind() == json::kind::uint64) // boost internally tracks two types of integers + return type::integer; + if (val.kind() == json::kind::double_) return type::number; + if (val.kind() == json::kind::string) return type::string; + if (val.kind() == json::kind::array) return type::array; + if (val.kind() == json::kind::object) return type::object; + + throw std::logic_error("invalid type"); + } + + static object_type as_object(const value_type& val) { + if (val.kind() != json::kind::object) throw std::bad_cast(); + return val.get_object(); + } + + static array_type as_array(const value_type& val) { + if (val.kind() != json::kind::array) throw std::bad_cast(); + return val.get_array(); + } + + static string_type as_string(const value_type& val) { + if (val.kind() != json::kind::string) throw std::bad_cast(); + return string_type{val.get_string()}; + } + + static integer_type as_integer(const value_type& val) { + switch (val.kind()) { + case json::kind::int64: return val.get_int64(); + case json::kind::uint64: return static_cast(val.get_uint64()); + default: throw std::bad_cast(); + } + } + + static boolean_type as_boolean(const value_type& val) { + if (val.kind() != json::kind::bool_) throw std::bad_cast(); + return val.get_bool(); + } + + static number_type as_number(const value_type& val) { + if (val.kind() != json::kind::double_) throw std::bad_cast(); + return val.get_double(); + } + + static bool parse(value_type& val, string_type str) { + val = json::parse(str); + return true; + } + + static std::string serialize(const value_type& val) { return json::serialize(val); } + }; + } // namespace traits +} // namespace jwt + +#endif // JWT_CPP_BOOSTJSON_TRAITS_H diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h new file mode 100644 index 00000000..47e12f5f --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/defaults.h @@ -0,0 +1,88 @@ +#ifndef JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H +#define JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [jsoncons](https://github.com/danielaparker/jsoncons) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk parse_jwk(const traits::danielaparker_jsoncons::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks parse_jwks(const traits::danielaparker_jsoncons::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_DANIELAPARKER_JSONCONS_DEFAULTS_H diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h new file mode 100644 index 00000000..be56740c --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/danielaparker-jsoncons/traits.h @@ -0,0 +1,123 @@ +#define JWT_DISABLE_PICOJSON +#define JSONCONS_NO_DEPRECATED + +#include "jwt-cpp/jwt.h" + +#include "jsoncons/json.hpp" + +#include + +namespace jwt { + namespace traits { + struct danielaparker_jsoncons { + // Needs at least https://github.com/danielaparker/jsoncons/commit/28c56b90ec7337f98a5b8942574590111a5e5831 + static_assert(jsoncons::version().minor >= 167, "A higher version of jsoncons is required!"); + + using json = jsoncons::json; + using value_type = json; + struct object_type : json::object { + // Add missing C++11 member types + // https://github.com/danielaparker/jsoncons/commit/1b1ceeb572f9a2db6d37cff47ac78a4f14e072e2#commitcomment-45391411 + using value_type = key_value_type; // Enable optional jwt-cpp methods + using mapped_type = key_value_type::value_type; + using size_type = size_t; // for implementing count + + object_type() = default; + object_type(const object_type&) = default; + explicit object_type(const json::object& o) : json::object(o) {} + object_type(object_type&&) = default; + explicit object_type(json::object&& o) : json::object(o) {} + ~object_type() = default; + object_type& operator=(const object_type& o) = default; + object_type& operator=(object_type&& o) noexcept = default; + + // Add missing C++11 subscription operator + mapped_type& operator[](const key_type& key) { + // https://github.com/microsoft/STL/blob/2914b4301c59dc7ffc09d16ac6f7979fde2b7f2c/stl/inc/map#L325 + return try_emplace(key).first->value(); + } + + // Add missing C++11 element access + const mapped_type& at(const key_type& key) const { + auto target = find(key); + if (target != end()) return target->value(); + + throw std::out_of_range("invalid key"); + } + + // Add missing C++11 lookup method + size_type count(const key_type& key) const { + struct compare { + bool operator()(const value_type& val, const key_type& key) const { return val.key() < key; } + bool operator()(const key_type& key, const value_type& val) const { return key < val.key(); } + }; + + // https://en.cppreference.com/w/cpp/algorithm/binary_search#Complexity + if (std::binary_search(this->begin(), this->end(), key, compare{})) return 1; + return 0; + } + }; + using array_type = json::array; + using string_type = std::string; // current limitation of traits implementation + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static jwt::json::type get_type(const json& val) { + using jwt::json::type; + + if (val.type() == jsoncons::json_type::bool_value) return type::boolean; + if (val.type() == jsoncons::json_type::int64_value) return type::integer; + if (val.type() == jsoncons::json_type::uint64_value) return type::integer; + if (val.type() == jsoncons::json_type::half_value) return type::number; + if (val.type() == jsoncons::json_type::double_value) return type::number; + if (val.type() == jsoncons::json_type::string_value) return type::string; + if (val.type() == jsoncons::json_type::array_value) return type::array; + if (val.type() == jsoncons::json_type::object_value) return type::object; + + throw std::logic_error("invalid type"); + } + + static object_type as_object(const json& val) { + if (val.type() != jsoncons::json_type::object_value) throw std::bad_cast(); + return object_type(val.object_value()); + } + + static array_type as_array(const json& val) { + if (val.type() != jsoncons::json_type::array_value) throw std::bad_cast(); + return val.array_value(); + } + + static string_type as_string(const json& val) { + if (val.type() != jsoncons::json_type::string_value) throw std::bad_cast(); + return val.as_string(); + } + + static number_type as_number(const json& val) { + if (get_type(val) != jwt::json::type::number) throw std::bad_cast(); + return val.as_double(); + } + + static integer_type as_integer(const json& val) { + if (get_type(val) != jwt::json::type::integer) throw std::bad_cast(); + return val.as(); + } + + static boolean_type as_boolean(const json& val) { + if (val.type() != jsoncons::json_type::bool_value) throw std::bad_cast(); + return val.as_bool(); + } + + static bool parse(json& val, const std::string& str) { + val = json::parse(str); + return true; + } + + static std::string serialize(const json& val) { + std::ostringstream os; + os << jsoncons::print(val); + return os.str(); + } + }; + } // namespace traits +} // namespace jwt diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/defaults.h.mustache b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/defaults.h.mustache new file mode 100644 index 00000000..ab2a8472 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/defaults.h.mustache @@ -0,0 +1,90 @@ +#ifndef JWT_CPP_{{traits_name_upper}}_DEFAULTS_H +#define JWT_CPP_{{traits_name_upper}}_DEFAULTS_H +{{#disable_default_traits}} + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif +{{/disable_default_traits}} + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [{{library_name}}]({{{library_url}}}) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk parse_jwk(const traits::{{traits_name}}::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks parse_jwks(const traits::{{traits_name}}::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_{{traits_name_upper}}_DEFAULTS_H diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h new file mode 100644 index 00000000..0c82133a --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/defaults.h @@ -0,0 +1,84 @@ +#ifndef JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H +#define JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [picojson](https://github.com/kazuho/picojson) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk parse_jwk(const traits::kazuho_picojson::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks parse_jwks(const traits::kazuho_picojson::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_KAZUHO_PICOJSON_DEFAULTS_H diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h new file mode 100644 index 00000000..2f96bfc0 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/kazuho-picojson/traits.h @@ -0,0 +1,76 @@ +#ifndef JWT_CPP_PICOJSON_TRAITS_H +#define JWT_CPP_PICOJSON_TRAITS_H + +#ifndef PICOJSON_USE_INT64 +#define PICOJSON_USE_INT64 +#endif +#include "picojson/picojson.h" + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif +#include "jwt-cpp/jwt.h" + +namespace jwt { + namespace traits { + struct kazuho_picojson { + using value_type = picojson::value; + using object_type = picojson::object; + using array_type = picojson::array; + using string_type = std::string; + using number_type = double; + using integer_type = int64_t; + using boolean_type = bool; + + static json::type get_type(const picojson::value& val) { + using json::type; + if (val.is()) return type::boolean; + if (val.is()) return type::integer; + if (val.is()) return type::number; + if (val.is()) return type::string; + if (val.is()) return type::array; + if (val.is()) return type::object; + + throw std::logic_error("invalid type"); + } + + static picojson::object as_object(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static picojson::array as_array(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_integer(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool as_boolean(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const picojson::value& val) { + if (!val.is()) throw std::bad_cast(); + return val.get(); + } + + static bool parse(picojson::value& val, const std::string& str) { + return picojson::parse(val, str).empty(); + } + + static std::string serialize(const picojson::value& val) { return val.serialize(); } + }; + } // namespace traits +} // namespace jwt + +#endif diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h new file mode 100644 index 00000000..c324075f --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/defaults.h @@ -0,0 +1,88 @@ +#ifndef JWT_CPP_NLOHMANN_JSON_DEFAULTS_H +#define JWT_CPP_NLOHMANN_JSON_DEFAULTS_H + +#ifndef JWT_DISABLE_PICOJSON +#define JWT_DISABLE_PICOJSON +#endif + +#include "traits.h" + +namespace jwt { + /** + * \brief a class to store a generic [JSON for Modern C++](https://github.com/nlohmann/json) value as claim + * + * This type is the specialization of the \ref basic_claim class which + * uses the standard template types. + */ + using claim = basic_claim; + + /** + * Create a verifier using the default clock + * \return verifier instance + */ + inline verifier verify() { + return verify(default_clock{}); + } + + /** + * Return a builder instance to create a new token + */ + inline builder create() { return builder(); } + +#ifndef JWT_DISABLE_BASE64 + /** + * Decode a token + * \param token Token to decode + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + inline decoded_jwt decode(const std::string& token) { + return decoded_jwt(token); + } +#endif + + /** + * Decode a token + * \tparam Decode is callabled, taking a string_type and returns a string_type. + * It should ensure the padding of the input and then base64url decode and + * return the results. + * \param token Token to decode + * \param decode The token to parse + * \return Decoded token + * \throw std::invalid_argument Token is not in correct format + * \throw std::runtime_error Base64 decoding failed or invalid json + */ + template + decoded_jwt decode(const std::string& token, Decode decode) { + return decoded_jwt(token, decode); + } + + /** + * Parse a jwk + * \param token JWK Token to parse + * \return Parsed JWK + * \throw std::runtime_error Token is not in correct format + */ + inline jwk parse_jwk(const traits::nlohmann_json::string_type& token) { + return jwk(token); + } + + /** + * Parse a jwks + * \param token JWKs Token to parse + * \return Parsed JWKs + * \throw std::runtime_error Token is not in correct format + */ + inline jwks parse_jwks(const traits::nlohmann_json::string_type& token) { + return jwks(token); + } + + /** + * This type is the specialization of the \ref verify_ops::verify_context class which + * uses the standard template types. + */ + using verify_context = verify_ops::verify_context; +} // namespace jwt + +#endif // JWT_CPP_NLOHMANN_JSON_DEFAULTS_H diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/traits.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/traits.h new file mode 100644 index 00000000..7cf48690 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/jwt-cpp/traits/nlohmann-json/traits.h @@ -0,0 +1,77 @@ +#ifndef JWT_CPP_NLOHMANN_JSON_TRAITS_H +#define JWT_CPP_NLOHMANN_JSON_TRAITS_H + +#include "jwt-cpp/jwt.h" +#include "nlohmann/json.hpp" + +namespace jwt { + namespace traits { + struct nlohmann_json { + using json = nlohmann::json; + using value_type = json; + using object_type = json::object_t; + using array_type = json::array_t; + using string_type = std::string; // current limitation of traits implementation + using number_type = json::number_float_t; + using integer_type = json::number_integer_t; + using boolean_type = json::boolean_t; + + static jwt::json::type get_type(const json& val) { + using jwt::json::type; + + if (val.type() == json::value_t::boolean) return type::boolean; + // nlohmann internally tracks two types of integers + if (val.type() == json::value_t::number_integer) return type::integer; + if (val.type() == json::value_t::number_unsigned) return type::integer; + if (val.type() == json::value_t::number_float) return type::number; + if (val.type() == json::value_t::string) return type::string; + if (val.type() == json::value_t::array) return type::array; + if (val.type() == json::value_t::object) return type::object; + + throw std::logic_error("invalid type"); + } + + static json::object_t as_object(const json& val) { + if (val.type() != json::value_t::object) throw std::bad_cast(); + return val.get(); + } + + static std::string as_string(const json& val) { + if (val.type() != json::value_t::string) throw std::bad_cast(); + return val.get(); + } + + static json::array_t as_array(const json& val) { + if (val.type() != json::value_t::array) throw std::bad_cast(); + return val.get(); + } + + static int64_t as_integer(const json& val) { + switch (val.type()) { + case json::value_t::number_integer: + case json::value_t::number_unsigned: return val.get(); + default: throw std::bad_cast(); + } + } + + static bool as_boolean(const json& val) { + if (val.type() != json::value_t::boolean) throw std::bad_cast(); + return val.get(); + } + + static double as_number(const json& val) { + if (val.type() != json::value_t::number_float) throw std::bad_cast(); + return val.get(); + } + + static bool parse(json& val, std::string str) { + val = json::parse(str.begin(), str.end()); + return true; + } + + static std::string serialize(const json& val) { return val.dump(); } + }; + } // namespace traits +} // namespace jwt + +#endif diff --git a/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/picojson/picojson.h b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/picojson/picojson.h new file mode 100644 index 00000000..76742fe0 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/jwt_cpp/include/picojson/picojson.h @@ -0,0 +1,1200 @@ +/* + * Copyright 2009-2010 Cybozu Labs, Inc. + * Copyright 2011-2014 Kazuho Oku + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef picojson_h +#define picojson_h + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// for isnan/isinf +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#ifdef _MSC_VER +#include +#elif defined(__INTEL_COMPILER) +#include +#else +#include +#endif +} +#endif + +#ifndef PICOJSON_USE_RVALUE_REFERENCE +#if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) +#define PICOJSON_USE_RVALUE_REFERENCE 1 +#else +#define PICOJSON_USE_RVALUE_REFERENCE 0 +#endif +#endif // PICOJSON_USE_RVALUE_REFERENCE + +#ifndef PICOJSON_NOEXCEPT +#if PICOJSON_USE_RVALUE_REFERENCE +#define PICOJSON_NOEXCEPT noexcept +#else +#define PICOJSON_NOEXCEPT throw() +#endif +#endif + +// experimental support for int64_t (see README.mkdn for detail) +#ifdef PICOJSON_USE_INT64 +#define __STDC_FORMAT_MACROS +#include +#if __cplusplus >= 201103L +#include +#else +extern "C" { +#include +} +#endif +#endif + +// to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 +#ifndef PICOJSON_USE_LOCALE +#define PICOJSON_USE_LOCALE 1 +#endif +#if PICOJSON_USE_LOCALE +extern "C" { +#include +} +#endif + +#ifndef PICOJSON_ASSERT +#define PICOJSON_ASSERT(e) \ + do { \ + if (!(e)) \ + throw std::runtime_error(#e); \ + } while (0) +#endif + +#ifdef _MSC_VER +#define SNPRINTF _snprintf_s +#pragma warning(push) +#pragma warning(disable : 4244) // conversion from int to char +#pragma warning(disable : 4127) // conditional expression is constant +#pragma warning(disable : 4702) // unreachable code +#pragma warning(disable : 4706) // assignment within conditional expression +#else +#define SNPRINTF snprintf +#endif + +namespace picojson { + +enum { + null_type, + boolean_type, + number_type, + string_type, + array_type, + object_type +#ifdef PICOJSON_USE_INT64 + , + int64_type +#endif +}; + +enum { INDENT_WIDTH = 2, DEFAULT_MAX_DEPTHS = 100 }; + +struct null {}; + +class value { +public: + typedef std::vector array; + typedef std::map object; + union _storage { + bool boolean_; + double number_; +#ifdef PICOJSON_USE_INT64 + int64_t int64_; +#endif + std::string *string_; + array *array_; + object *object_; + }; + +protected: + int type_; + _storage u_; + +public: + value(); + value(int type, bool); + explicit value(bool b); +#ifdef PICOJSON_USE_INT64 + explicit value(int64_t i); +#endif + explicit value(double n); + explicit value(const std::string &s); + explicit value(const array &a); + explicit value(const object &o); +#if PICOJSON_USE_RVALUE_REFERENCE + explicit value(std::string &&s); + explicit value(array &&a); + explicit value(object &&o); +#endif + explicit value(const char *s); + value(const char *s, size_t len); + ~value(); + value(const value &x); + value &operator=(const value &x); +#if PICOJSON_USE_RVALUE_REFERENCE + value(value &&x) PICOJSON_NOEXCEPT; + value &operator=(value &&x) PICOJSON_NOEXCEPT; +#endif + void swap(value &x) PICOJSON_NOEXCEPT; + template bool is() const; + template const T &get() const; + template T &get(); + template void set(const T &); +#if PICOJSON_USE_RVALUE_REFERENCE + template void set(T &&); +#endif + bool evaluate_as_boolean() const; + const value &get(const size_t idx) const; + const value &get(const std::string &key) const; + value &get(const size_t idx); + value &get(const std::string &key); + + bool contains(const size_t idx) const; + bool contains(const std::string &key) const; + std::string to_str() const; + template void serialize(Iter os, bool prettify = false) const; + std::string serialize(bool prettify = false) const; + +private: + template value(const T *); // intentionally defined to block implicit conversion of pointer to bool + template static void _indent(Iter os, int indent); + template void _serialize(Iter os, int indent) const; + std::string _serialize(int indent) const; + void clear(); +}; + +typedef value::array array; +typedef value::object object; + +inline value::value() : type_(null_type), u_() { +} + +inline value::value(int type, bool) : type_(type), u_() { + switch (type) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(boolean_, false); + INIT(number_, 0.0); +#ifdef PICOJSON_USE_INT64 + INIT(int64_, 0); +#endif + INIT(string_, new std::string()); + INIT(array_, new array()); + INIT(object_, new object()); +#undef INIT + default: + break; + } +} + +inline value::value(bool b) : type_(boolean_type), u_() { + u_.boolean_ = b; +} + +#ifdef PICOJSON_USE_INT64 +inline value::value(int64_t i) : type_(int64_type), u_() { + u_.int64_ = i; +} +#endif + +inline value::value(double n) : type_(number_type), u_() { + if ( +#ifdef _MSC_VER + !_finite(n) +#elif __cplusplus >= 201103L + std::isnan(n) || std::isinf(n) +#else + isnan(n) || isinf(n) +#endif + ) { + throw std::overflow_error(""); + } + u_.number_ = n; +} + +inline value::value(const std::string &s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const array &a) : type_(array_type), u_() { + u_.array_ = new array(a); +} + +inline value::value(const object &o) : type_(object_type), u_() { + u_.object_ = new object(o); +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(std::string &&s) : type_(string_type), u_() { + u_.string_ = new std::string(std::move(s)); +} + +inline value::value(array &&a) : type_(array_type), u_() { + u_.array_ = new array(std::move(a)); +} + +inline value::value(object &&o) : type_(object_type), u_() { + u_.object_ = new object(std::move(o)); +} +#endif + +inline value::value(const char *s) : type_(string_type), u_() { + u_.string_ = new std::string(s); +} + +inline value::value(const char *s, size_t len) : type_(string_type), u_() { + u_.string_ = new std::string(s, len); +} + +inline void value::clear() { + switch (type_) { +#define DEINIT(p) \ + case p##type: \ + delete u_.p; \ + break + DEINIT(string_); + DEINIT(array_); + DEINIT(object_); +#undef DEINIT + default: + break; + } +} + +inline value::~value() { + clear(); +} + +inline value::value(const value &x) : type_(x.type_), u_() { + switch (type_) { +#define INIT(p, v) \ + case p##type: \ + u_.p = v; \ + break + INIT(string_, new std::string(*x.u_.string_)); + INIT(array_, new array(*x.u_.array_)); + INIT(object_, new object(*x.u_.object_)); +#undef INIT + default: + u_ = x.u_; + break; + } +} + +inline value &value::operator=(const value &x) { + if (this != &x) { + value t(x); + swap(t); + } + return *this; +} + +#if PICOJSON_USE_RVALUE_REFERENCE +inline value::value(value &&x) PICOJSON_NOEXCEPT : type_(null_type), u_() { + swap(x); +} +inline value &value::operator=(value &&x) PICOJSON_NOEXCEPT { + swap(x); + return *this; +} +#endif +inline void value::swap(value &x) PICOJSON_NOEXCEPT { + std::swap(type_, x.type_); + std::swap(u_, x.u_); +} + +#define IS(ctype, jtype) \ + template <> inline bool value::is() const { \ + return type_ == jtype##_type; \ + } +IS(null, null) +IS(bool, boolean) +#ifdef PICOJSON_USE_INT64 +IS(int64_t, int64) +#endif +IS(std::string, string) +IS(array, array) +IS(object, object) +#undef IS +template <> inline bool value::is() const { + return type_ == number_type +#ifdef PICOJSON_USE_INT64 + || type_ == int64_type +#endif + ; +} + +#define GET(ctype, var) \ + template <> inline const ctype &value::get() const { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } \ + template <> inline ctype &value::get() { \ + PICOJSON_ASSERT("type mismatch! call is() before get()" && is()); \ + return var; \ + } +GET(bool, u_.boolean_) +GET(std::string, *u_.string_) +GET(array, *u_.array_) +GET(object, *u_.object_) +#ifdef PICOJSON_USE_INT64 +GET(double, + (type_ == int64_type && (const_cast(this)->type_ = number_type, (const_cast(this)->u_.number_ = u_.int64_)), + u_.number_)) +GET(int64_t, u_.int64_) +#else +GET(double, u_.number_) +#endif +#undef GET + +#define SET(ctype, jtype, setter) \ + template <> inline void value::set(const ctype &_val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +SET(bool, boolean, u_.boolean_ = _val;) +SET(std::string, string, u_.string_ = new std::string(_val);) +SET(array, array, u_.array_ = new array(_val);) +SET(object, object, u_.object_ = new object(_val);) +SET(double, number, u_.number_ = _val;) +#ifdef PICOJSON_USE_INT64 +SET(int64_t, int64, u_.int64_ = _val;) +#endif +#undef SET + +#if PICOJSON_USE_RVALUE_REFERENCE +#define MOVESET(ctype, jtype, setter) \ + template <> inline void value::set(ctype && _val) { \ + clear(); \ + type_ = jtype##_type; \ + setter \ + } +MOVESET(std::string, string, u_.string_ = new std::string(std::move(_val));) +MOVESET(array, array, u_.array_ = new array(std::move(_val));) +MOVESET(object, object, u_.object_ = new object(std::move(_val));) +#undef MOVESET +#endif + +inline bool value::evaluate_as_boolean() const { + switch (type_) { + case null_type: + return false; + case boolean_type: + return u_.boolean_; + case number_type: + return u_.number_ != 0; +#ifdef PICOJSON_USE_INT64 + case int64_type: + return u_.int64_ != 0; +#endif + case string_type: + return !u_.string_->empty(); + default: + return true; + } +} + +inline const value &value::get(const size_t idx) const { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline value &value::get(const size_t idx) { + static value s_null; + PICOJSON_ASSERT(is()); + return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; +} + +inline const value &value::get(const std::string &key) const { + static value s_null; + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline value &value::get(const std::string &key) { + static value s_null; + PICOJSON_ASSERT(is()); + object::iterator i = u_.object_->find(key); + return i != u_.object_->end() ? i->second : s_null; +} + +inline bool value::contains(const size_t idx) const { + PICOJSON_ASSERT(is()); + return idx < u_.array_->size(); +} + +inline bool value::contains(const std::string &key) const { + PICOJSON_ASSERT(is()); + object::const_iterator i = u_.object_->find(key); + return i != u_.object_->end(); +} + +inline std::string value::to_str() const { + switch (type_) { + case null_type: + return "null"; + case boolean_type: + return u_.boolean_ ? "true" : "false"; +#ifdef PICOJSON_USE_INT64 + case int64_type: { + char buf[sizeof("-9223372036854775808")]; + SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); + return buf; + } +#endif + case number_type: { + char buf[256]; + double tmp; + SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); +#if PICOJSON_USE_LOCALE + char *decimal_point = localeconv()->decimal_point; + if (strcmp(decimal_point, ".") != 0) { + size_t decimal_point_len = strlen(decimal_point); + for (char *p = buf; *p != '\0'; ++p) { + if (strncmp(p, decimal_point, decimal_point_len) == 0) { + return std::string(buf, p) + "." + (p + decimal_point_len); + } + } + } +#endif + return buf; + } + case string_type: + return *u_.string_; + case array_type: + return "array"; + case object_type: + return "object"; + default: + PICOJSON_ASSERT(0); +#ifdef _MSC_VER + __assume(0); +#endif + } + return std::string(); +} + +template void copy(const std::string &s, Iter oi) { + std::copy(s.begin(), s.end(), oi); +} + +template struct serialize_str_char { + Iter oi; + void operator()(char c) { + switch (c) { +#define MAP(val, sym) \ + case val: \ + copy(sym, oi); \ + break + MAP('"', "\\\""); + MAP('\\', "\\\\"); + MAP('/', "\\/"); + MAP('\b', "\\b"); + MAP('\f', "\\f"); + MAP('\n', "\\n"); + MAP('\r', "\\r"); + MAP('\t', "\\t"); +#undef MAP + default: + if (static_cast(c) < 0x20 || c == 0x7f) { + char buf[7]; + SNPRINTF(buf, sizeof(buf), "\\u%04x", c & 0xff); + copy(buf, buf + 6, oi); + } else { + *oi++ = c; + } + break; + } + } +}; + +template void serialize_str(const std::string &s, Iter oi) { + *oi++ = '"'; + serialize_str_char process_char = {oi}; + std::for_each(s.begin(), s.end(), process_char); + *oi++ = '"'; +} + +template void value::serialize(Iter oi, bool prettify) const { + return _serialize(oi, prettify ? 0 : -1); +} + +inline std::string value::serialize(bool prettify) const { + return _serialize(prettify ? 0 : -1); +} + +template void value::_indent(Iter oi, int indent) { + *oi++ = '\n'; + for (int i = 0; i < indent * INDENT_WIDTH; ++i) { + *oi++ = ' '; + } +} + +template void value::_serialize(Iter oi, int indent) const { + switch (type_) { + case string_type: + serialize_str(*u_.string_, oi); + break; + case array_type: { + *oi++ = '['; + if (indent != -1) { + ++indent; + } + for (array::const_iterator i = u_.array_->begin(); i != u_.array_->end(); ++i) { + if (i != u_.array_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + i->_serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.array_->empty()) { + _indent(oi, indent); + } + } + *oi++ = ']'; + break; + } + case object_type: { + *oi++ = '{'; + if (indent != -1) { + ++indent; + } + for (object::const_iterator i = u_.object_->begin(); i != u_.object_->end(); ++i) { + if (i != u_.object_->begin()) { + *oi++ = ','; + } + if (indent != -1) { + _indent(oi, indent); + } + serialize_str(i->first, oi); + *oi++ = ':'; + if (indent != -1) { + *oi++ = ' '; + } + i->second._serialize(oi, indent); + } + if (indent != -1) { + --indent; + if (!u_.object_->empty()) { + _indent(oi, indent); + } + } + *oi++ = '}'; + break; + } + default: + copy(to_str(), oi); + break; + } + if (indent == 0) { + *oi++ = '\n'; + } +} + +inline std::string value::_serialize(int indent) const { + std::string s; + _serialize(std::back_inserter(s), indent); + return s; +} + +template class input { +protected: + Iter cur_, end_; + bool consumed_; + int line_; + +public: + input(const Iter &first, const Iter &last) : cur_(first), end_(last), consumed_(false), line_(1) { + } + int getc() { + if (consumed_) { + if (*cur_ == '\n') { + ++line_; + } + ++cur_; + } + if (cur_ == end_) { + consumed_ = false; + return -1; + } + consumed_ = true; + return *cur_ & 0xff; + } + void ungetc() { + consumed_ = false; + } + Iter cur() const { + if (consumed_) { + input *self = const_cast *>(this); + self->consumed_ = false; + ++self->cur_; + } + return cur_; + } + int line() const { + return line_; + } + void skip_ws() { + while (1) { + int ch = getc(); + if (!(ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { + ungetc(); + break; + } + } + } + bool expect(const int expected) { + skip_ws(); + if (getc() != expected) { + ungetc(); + return false; + } + return true; + } + bool match(const std::string &pattern) { + for (std::string::const_iterator pi(pattern.begin()); pi != pattern.end(); ++pi) { + if (getc() != *pi) { + ungetc(); + return false; + } + } + return true; + } +}; + +template inline int _parse_quadhex(input &in) { + int uni_ch = 0, hex; + for (int i = 0; i < 4; i++) { + if ((hex = in.getc()) == -1) { + return -1; + } + if ('0' <= hex && hex <= '9') { + hex -= '0'; + } else if ('A' <= hex && hex <= 'F') { + hex -= 'A' - 0xa; + } else if ('a' <= hex && hex <= 'f') { + hex -= 'a' - 0xa; + } else { + in.ungetc(); + return -1; + } + uni_ch = uni_ch * 16 + hex; + } + return uni_ch; +} + +template inline bool _parse_codepoint(String &out, input &in) { + int uni_ch; + if ((uni_ch = _parse_quadhex(in)) == -1) { + return false; + } + if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { + if (0xdc00 <= uni_ch) { + // a second 16-bit of a surrogate pair appeared + return false; + } + // first 16-bit of surrogate pair, get the next one + if (in.getc() != '\\' || in.getc() != 'u') { + in.ungetc(); + return false; + } + int second = _parse_quadhex(in); + if (!(0xdc00 <= second && second <= 0xdfff)) { + return false; + } + uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); + uni_ch += 0x10000; + } + if (uni_ch < 0x80) { + out.push_back(static_cast(uni_ch)); + } else { + if (uni_ch < 0x800) { + out.push_back(static_cast(0xc0 | (uni_ch >> 6))); + } else { + if (uni_ch < 0x10000) { + out.push_back(static_cast(0xe0 | (uni_ch >> 12))); + } else { + out.push_back(static_cast(0xf0 | (uni_ch >> 18))); + out.push_back(static_cast(0x80 | ((uni_ch >> 12) & 0x3f))); + } + out.push_back(static_cast(0x80 | ((uni_ch >> 6) & 0x3f))); + } + out.push_back(static_cast(0x80 | (uni_ch & 0x3f))); + } + return true; +} + +template inline bool _parse_string(String &out, input &in) { + while (1) { + int ch = in.getc(); + if (ch < ' ') { + in.ungetc(); + return false; + } else if (ch == '"') { + return true; + } else if (ch == '\\') { + if ((ch = in.getc()) == -1) { + return false; + } + switch (ch) { +#define MAP(sym, val) \ + case sym: \ + out.push_back(val); \ + break + MAP('"', '\"'); + MAP('\\', '\\'); + MAP('/', '/'); + MAP('b', '\b'); + MAP('f', '\f'); + MAP('n', '\n'); + MAP('r', '\r'); + MAP('t', '\t'); +#undef MAP + case 'u': + if (!_parse_codepoint(out, in)) { + return false; + } + break; + default: + return false; + } + } else { + out.push_back(static_cast(ch)); + } + } + return false; +} + +template inline bool _parse_array(Context &ctx, input &in) { + if (!ctx.parse_array_start()) { + return false; + } + size_t idx = 0; + if (in.expect(']')) { + return ctx.parse_array_stop(idx); + } + do { + if (!ctx.parse_array_item(in, idx)) { + return false; + } + idx++; + } while (in.expect(',')); + return in.expect(']') && ctx.parse_array_stop(idx); +} + +template inline bool _parse_object(Context &ctx, input &in) { + if (!ctx.parse_object_start()) { + return false; + } + if (in.expect('}')) { + return ctx.parse_object_stop(); + } + do { + std::string key; + if (!in.expect('"') || !_parse_string(key, in) || !in.expect(':')) { + return false; + } + if (!ctx.parse_object_item(in, key)) { + return false; + } + } while (in.expect(',')); + return in.expect('}') && ctx.parse_object_stop(); +} + +template inline std::string _parse_number(input &in) { + std::string num_str; + while (1) { + int ch = in.getc(); + if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' || ch == 'e' || ch == 'E') { + num_str.push_back(static_cast(ch)); + } else if (ch == '.') { +#if PICOJSON_USE_LOCALE + num_str += localeconv()->decimal_point; +#else + num_str.push_back('.'); +#endif + } else { + in.ungetc(); + break; + } + } + return num_str; +} + +template inline bool _parse(Context &ctx, input &in) { + in.skip_ws(); + int ch = in.getc(); + switch (ch) { +#define IS(ch, text, op) \ + case ch: \ + if (in.match(text) && op) { \ + return true; \ + } else { \ + return false; \ + } + IS('n', "ull", ctx.set_null()); + IS('f', "alse", ctx.set_bool(false)); + IS('t', "rue", ctx.set_bool(true)); +#undef IS + case '"': + return ctx.parse_string(in); + case '[': + return _parse_array(ctx, in); + case '{': + return _parse_object(ctx, in); + default: + if (('0' <= ch && ch <= '9') || ch == '-') { + double f; + char *endp; + in.ungetc(); + std::string num_str(_parse_number(in)); + if (num_str.empty()) { + return false; + } +#ifdef PICOJSON_USE_INT64 + { + errno = 0; + intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); + if (errno == 0 && std::numeric_limits::min() <= ival && ival <= std::numeric_limits::max() && + endp == num_str.c_str() + num_str.size()) { + ctx.set_int64(ival); + return true; + } + } +#endif + f = strtod(num_str.c_str(), &endp); + if (endp == num_str.c_str() + num_str.size()) { + ctx.set_number(f); + return true; + } + return false; + } + break; + } + in.ungetc(); + return false; +} + +class deny_parse_context { +public: + bool set_null() { + return false; + } + bool set_bool(bool) { + return false; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t) { + return false; + } +#endif + bool set_number(double) { + return false; + } + template bool parse_string(input &) { + return false; + } + bool parse_array_start() { + return false; + } + template bool parse_array_item(input &, size_t) { + return false; + } + bool parse_array_stop(size_t) { + return false; + } + bool parse_object_start() { + return false; + } + template bool parse_object_item(input &, const std::string &) { + return false; + } +}; + +class default_parse_context { +protected: + value *out_; + size_t depths_; + +public: + default_parse_context(value *out, size_t depths = DEFAULT_MAX_DEPTHS) : out_(out), depths_(depths) { + } + bool set_null() { + *out_ = value(); + return true; + } + bool set_bool(bool b) { + *out_ = value(b); + return true; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t i) { + *out_ = value(i); + return true; + } +#endif + bool set_number(double f) { + *out_ = value(f); + return true; + } + template bool parse_string(input &in) { + *out_ = value(string_type, false); + return _parse_string(out_->get(), in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + *out_ = value(array_type, false); + return true; + } + template bool parse_array_item(input &in, size_t) { + array &a = out_->get(); + a.push_back(value()); + default_parse_context ctx(&a.back(), depths_); + return _parse(ctx, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + *out_ = value(object_type, false); + return true; + } + template bool parse_object_item(input &in, const std::string &key) { + object &o = out_->get(); + default_parse_context ctx(&o[key], depths_); + return _parse(ctx, in); + } + bool parse_object_stop() { + ++depths_; + return true; + } + +private: + default_parse_context(const default_parse_context &); + default_parse_context &operator=(const default_parse_context &); +}; + +class null_parse_context { +protected: + size_t depths_; + +public: + struct dummy_str { + void push_back(int) { + } + }; + +public: + null_parse_context(size_t depths = DEFAULT_MAX_DEPTHS) : depths_(depths) { + } + bool set_null() { + return true; + } + bool set_bool(bool) { + return true; + } +#ifdef PICOJSON_USE_INT64 + bool set_int64(int64_t) { + return true; + } +#endif + bool set_number(double) { + return true; + } + template bool parse_string(input &in) { + dummy_str s; + return _parse_string(s, in); + } + bool parse_array_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_array_item(input &in, size_t) { + return _parse(*this, in); + } + bool parse_array_stop(size_t) { + ++depths_; + return true; + } + bool parse_object_start() { + if (depths_ == 0) + return false; + --depths_; + return true; + } + template bool parse_object_item(input &in, const std::string &) { + ++depths_; + return _parse(*this, in); + } + bool parse_object_stop() { + return true; + } + +private: + null_parse_context(const null_parse_context &); + null_parse_context &operator=(const null_parse_context &); +}; + +// obsolete, use the version below +template inline std::string parse(value &out, Iter &pos, const Iter &last) { + std::string err; + pos = parse(out, pos, last, &err); + return err; +} + +template inline Iter _parse(Context &ctx, const Iter &first, const Iter &last, std::string *err) { + input in(first, last); + if (!_parse(ctx, in) && err != NULL) { + char buf[64]; + SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); + *err = buf; + while (1) { + int ch = in.getc(); + if (ch == -1 || ch == '\n') { + break; + } else if (ch >= ' ') { + err->push_back(static_cast(ch)); + } + } + } + return in.cur(); +} + +template inline Iter parse(value &out, const Iter &first, const Iter &last, std::string *err) { + default_parse_context ctx(&out); + return _parse(ctx, first, last, err); +} + +inline std::string parse(value &out, const std::string &s) { + std::string err; + parse(out, s.begin(), s.end(), &err); + return err; +} + +inline std::string parse(value &out, std::istream &is) { + std::string err; + parse(out, std::istreambuf_iterator(is.rdbuf()), std::istreambuf_iterator(), &err); + return err; +} + +template struct last_error_t { static std::string s; }; +template std::string last_error_t::s; + +inline void set_last_error(const std::string &s) { + last_error_t::s = s; +} + +inline const std::string &get_last_error() { + return last_error_t::s; +} + +inline bool operator==(const value &x, const value &y) { + if (x.is()) + return y.is(); +#define PICOJSON_CMP(type) \ + if (x.is()) \ + return y.is() && x.get() == y.get() + PICOJSON_CMP(bool); + PICOJSON_CMP(double); + PICOJSON_CMP(std::string); + PICOJSON_CMP(array); + PICOJSON_CMP(object); +#undef PICOJSON_CMP + PICOJSON_ASSERT(0); +#ifdef _MSC_VER + __assume(0); +#endif + return false; +} + +inline bool operator!=(const value &x, const value &y) { + return !(x == y); +} +} + +#if !PICOJSON_USE_RVALUE_REFERENCE +namespace std { +template <> inline void swap(picojson::value &x, picojson::value &y) { + x.swap(y); +} +} +#endif + +inline std::istream &operator>>(std::istream &is, picojson::value &x) { + picojson::set_last_error(std::string()); + const std::string err(picojson::parse(x, is)); + if (!err.empty()) { + picojson::set_last_error(err); + is.setstate(std::ios::failbit); + } + return is; +} + +inline std::ostream &operator<<(std::ostream &os, const picojson::value &x) { + x.serialize(std::ostream_iterator(os)); + return os; +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif diff --git a/src/ros2_medkit_gateway/src/vendored/tl_expected/LICENSE b/src/ros2_medkit_gateway/src/vendored/tl_expected/LICENSE new file mode 100644 index 00000000..0e259d42 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/tl_expected/LICENSE @@ -0,0 +1,121 @@ +Creative Commons Legal Code + +CC0 1.0 Universal + + CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE + LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN + ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS + INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES + REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS + PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM + THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED + HEREUNDER. + +Statement of Purpose + +The laws of most jurisdictions throughout the world automatically confer +exclusive Copyright and Related Rights (defined below) upon the creator +and subsequent owner(s) (each and all, an "owner") of an original work of +authorship and/or a database (each, a "Work"). + +Certain owners wish to permanently relinquish those rights to a Work for +the purpose of contributing to a commons of creative, cultural and +scientific works ("Commons") that the public can reliably and without fear +of later claims of infringement build upon, modify, incorporate in other +works, reuse and redistribute as freely as possible in any form whatsoever +and for any purposes, including without limitation commercial purposes. +These owners may contribute to the Commons to promote the ideal of a free +culture and the further production of creative, cultural and scientific +works, or to gain reputation or greater distribution for their Work in +part through the use and efforts of others. + +For these and/or other purposes and motivations, and without any +expectation of additional consideration or compensation, the person +associating CC0 with a Work (the "Affirmer"), to the extent that he or she +is an owner of Copyright and Related Rights in the Work, voluntarily +elects to apply CC0 to the Work and publicly distribute the Work under its +terms, with knowledge of his or her Copyright and Related Rights in the +Work and the meaning and intended legal effect of CC0 on those rights. + +1. Copyright and Related Rights. A Work made available under CC0 may be +protected by copyright and related or neighboring rights ("Copyright and +Related Rights"). Copyright and Related Rights include, but are not +limited to, the following: + + i. the right to reproduce, adapt, distribute, perform, display, + communicate, and translate a Work; + ii. moral rights retained by the original author(s) and/or performer(s); +iii. publicity and privacy rights pertaining to a person's image or + likeness depicted in a Work; + iv. rights protecting against unfair competition in regards to a Work, + subject to the limitations in paragraph 4(a), below; + v. rights protecting the extraction, dissemination, use and reuse of data + in a Work; + vi. database rights (such as those arising under Directive 96/9/EC of the + European Parliament and of the Council of 11 March 1996 on the legal + protection of databases, and under any national implementation + thereof, including any amended or successor version of such + directive); and +vii. other similar, equivalent or corresponding rights throughout the + world based on applicable law or treaty, and any national + implementations thereof. + +2. Waiver. To the greatest extent permitted by, but not in contravention +of, applicable law, Affirmer hereby overtly, fully, permanently, +irrevocably and unconditionally waives, abandons, and surrenders all of +Affirmer's Copyright and Related Rights and associated claims and causes +of action, whether now known or unknown (including existing as well as +future claims and causes of action), in the Work (i) in all territories +worldwide, (ii) for the maximum duration provided by applicable law or +treaty (including future time extensions), (iii) in any current or future +medium and for any number of copies, and (iv) for any purpose whatsoever, +including without limitation commercial, advertising or promotional +purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each +member of the public at large and to the detriment of Affirmer's heirs and +successors, fully intending that such Waiver shall not be subject to +revocation, rescission, cancellation, termination, or any other legal or +equitable action to disrupt the quiet enjoyment of the Work by the public +as contemplated by Affirmer's express Statement of Purpose. + +3. Public License Fallback. Should any part of the Waiver for any reason +be judged legally invalid or ineffective under applicable law, then the +Waiver shall be preserved to the maximum extent permitted taking into +account Affirmer's express Statement of Purpose. In addition, to the +extent the Waiver is so judged Affirmer hereby grants to each affected +person a royalty-free, non transferable, non sublicensable, non exclusive, +irrevocable and unconditional license to exercise Affirmer's Copyright and +Related Rights in the Work (i) in all territories worldwide, (ii) for the +maximum duration provided by applicable law or treaty (including future +time extensions), (iii) in any current or future medium and for any number +of copies, and (iv) for any purpose whatsoever, including without +limitation commercial, advertising or promotional purposes (the +"License"). The License shall be deemed effective as of the date CC0 was +applied by Affirmer to the Work. Should any part of the License for any +reason be judged legally invalid or ineffective under applicable law, such +partial invalidity or ineffectiveness shall not invalidate the remainder +of the License, and in such case Affirmer hereby affirms that he or she +will not (i) exercise any of his or her remaining Copyright and Related +Rights in the Work or (ii) assert any associated claims and causes of +action with respect to the Work, in either case contrary to Affirmer's +express Statement of Purpose. + +4. Limitations and Disclaimers. + + a. No trademark or patent rights held by Affirmer are waived, abandoned, + surrendered, licensed or otherwise affected by this document. + b. Affirmer offers the Work as-is and makes no representations or + warranties of any kind concerning the Work, express, implied, + statutory or otherwise, including without limitation warranties of + title, merchantability, fitness for a particular purpose, non + infringement, or the absence of latent or other defects, accuracy, or + the present or absence of errors, whether or not discoverable, all to + the greatest extent permissible under applicable law. + c. Affirmer disclaims responsibility for clearing rights of other persons + that may apply to the Work or any use thereof, including without + limitation any person's Copyright and Related Rights in the Work. + Further, Affirmer disclaims responsibility for obtaining any necessary + consents, permissions or other rights required for any use of the + Work. + d. Affirmer understands and acknowledges that Creative Commons is not a + party to this document and has no duty or obligation with respect to + this CC0 or use of the Work. diff --git a/src/ros2_medkit_gateway/src/vendored/tl_expected/include/tl/expected.hpp b/src/ros2_medkit_gateway/src/vendored/tl_expected/include/tl/expected.hpp new file mode 100644 index 00000000..59e59aa1 --- /dev/null +++ b/src/ros2_medkit_gateway/src/vendored/tl_expected/include/tl/expected.hpp @@ -0,0 +1,2475 @@ +/// +// 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 3 +#define TL_EXPECTED_VERSION_PATCH 1 + +#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 + +#ifdef _MSVC_LANG +#define TL_CPLUSPLUS _MSVC_LANG +#else +#define TL_CPLUSPLUS __cplusplus +#endif + +#if !defined(TL_ASSERT) +//can't have assert in constexpr in C++11 and GCC 4.9 has a compiler bug +#if (TL_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 TL_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 (TL_CPLUSPLUS == 201103L || defined(TL_EXPECTED_MSVC2015) || \ + defined(TL_EXPECTED_GCC49)) +#define TL_EXPECTED_11_CONSTEXPR +#else +#define TL_EXPECTED_11_CONSTEXPR constexpr +#endif + +#if TL_CPLUSPLUS >= 201703L +#define TL_EXPECTED_NODISCARD [[nodiscard]] +#else +#define TL_EXPECTED_NODISCARD +#endif + +namespace tl { +template class TL_EXPECTED_NODISCARD 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{}; + +#ifdef TL_EXPECTED_EXCEPTIONS_ENABLED +#define TL_EXPECTED_THROW_EXCEPTION(e) throw((e)); +#else +#define TL_EXPECTED_THROW_EXCEPTION(e) std::terminate(); +#endif + +namespace detail { +#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(const expected_storage_base &) = default; + expected_storage_base(expected_storage_base &&) = default; + expected_storage_base &operator=(const expected_storage_base &) = default; + expected_storage_base &operator=(expected_storage_base &&) = default; + ~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(const expected_storage_base &) = default; + expected_storage_base(expected_storage_base &&) = default; + expected_storage_base &operator=(const expected_storage_base &) = default; + expected_storage_base &operator=(expected_storage_base &&) = default; + ~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(const expected_storage_base &) = default; + expected_storage_base(expected_storage_base &&) = default; + expected_storage_base &operator=(const expected_storage_base &) = default; + expected_storage_base &operator=(expected_storage_base &&) = default; + ~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(const expected_storage_base &) = default; + expected_storage_base(expected_storage_base &&) = default; + expected_storage_base &operator=(const expected_storage_base &) = default; + expected_storage_base &operator=(expected_storage_base &&) = default; + ~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(const expected_storage_base &) = default; + expected_storage_base(expected_storage_base &&) = default; + expected_storage_base &operator=(const expected_storage_base &) = default; + expected_storage_base &operator=(expected_storage_base &&) = default; + ~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, + bool = (is_copy_constructible_or_void::value && + std::is_copy_constructible::value)> +struct expected_copy_base : expected_operations_base { + using expected_operations_base::expected_operations_base; +}; + +// This specialization is for when T or E are non-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, + bool = (is_copy_constructible_or_void::value && + std::is_copy_constructible::value && + is_copy_assignable_or_void::value && + std::is_copy_assignable::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 +// constructor 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 TL_EXPECTED_NODISCARD 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()) + TL_EXPECTED_THROW_EXCEPTION(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR U &value() & { + if (!has_value()) + TL_EXPECTED_THROW_EXCEPTION(bad_expected_access(err().value())); + return val(); + } + template ::value> * = nullptr> + TL_EXPECTED_11_CONSTEXPR const U &&value() const && { + if (!has_value()) + TL_EXPECTED_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()) + TL_EXPECTED_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/src/ros2_medkit_msgs/CHANGELOG.rst b/src/ros2_medkit_msgs/CHANGELOG.rst new file mode 100644 index 00000000..20b57ada --- /dev/null +++ b/src/ros2_medkit_msgs/CHANGELOG.rst @@ -0,0 +1,25 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_msgs +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Forthcoming +----------- +* Initial rosdistro release +* Fault management messages: + + * Fault.msg - Core fault data model with severity levels (INFO/WARN/ERROR/CRITICAL) + and debounce-based status lifecycle (PREFAILED/PREPASSED/CONFIRMED/HEALED/CLEARED) + * FaultEvent.msg - Real-time event notifications (EVENT_CONFIRMED/EVENT_CLEARED/EVENT_UPDATED) + with auto-cleared correlation codes + * MutedFaultInfo.msg - Fault correlation muting metadata + * ClusterInfo.msg - Fault clustering information + +* Fault management services: + + * ReportFault.srv - Report fault events with FAILED/PASSED event model + * GetFaults.srv - Query faults with filtering by severity, status, and correlation data + * ClearFault.srv - Clear/acknowledge faults by fault_code + * GetSnapshots.srv - Retrieve topic snapshots captured at fault time + * GetRosbag.srv - Retrieve rosbag recordings associated with faults + +* Contributors: Bartosz Burda, Michal Faferek diff --git a/src/ros2_medkit_serialization/CHANGELOG.rst b/src/ros2_medkit_serialization/CHANGELOG.rst new file mode 100644 index 00000000..74f0f28a --- /dev/null +++ b/src/ros2_medkit_serialization/CHANGELOG.rst @@ -0,0 +1,16 @@ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Changelog for package ros2_medkit_serialization +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Forthcoming +----------- +* Initial rosdistro release +* Runtime JSON to ROS 2 message serialization using vendored dynmsg C++ API +* TypeCache - thread-safe caching of ROS type introspection data with shared_mutex + for read concurrency +* JsonSerializer - bidirectional JSON <-> ROS message conversion via dynmsg YAML bridge, + including CDR serialization/deserialization for GenericClient/GenericSubscription +* ServiceActionTypes - helper utilities for resolving service and action internal types + (request/response, goal/result/feedback) +* SerializationError exception hierarchy for structured error handling +* Contributors: Bartosz Burda