diff --git a/.gitignore b/.gitignore index fccfd589a1729b5b0556a0ceb3c65141fde675d2..02bce2e92c459988286bf0fede31144a407d64a2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ *.iml bin/ Gopkg.lock -vendor/ +/vendor temp/ yarn-error.log /.project diff --git a/cpp/.clang-tidy b/cpp/.clang-tidy index 756a03e01eb88879438b18d2cfd27e75c3092d8b..09a43d11558b23029276572fdd249961288076e0 100644 --- a/cpp/.clang-tidy +++ b/cpp/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize-*,readability-*,-readability-container-size-empty,-readability-magic-numbers' +Checks: 'clang-diagnostic-*,clang-analyzer-*,modernize-*,-modernize-use-nodiscard,readability-*,-readability-container-size-empty,-readability-magic-numbers' WarningsAsErrors: '' HeaderFilterRegex: '' AnalyzeTemporaryDtors: false diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 81ddf56dc84ab8900038e8c42d02b11bc720bcf4..abf9b756454dc84725418d3d5426339b5354583f 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -1,16 +1,13 @@ cmake_minimum_required (VERSION 3.16.5) -if(WIN32) - set(CMAKE_IGNORE_PATH "C:/Strawberry/c/bin") -endif() - project (vereign) # Options +# FIXME: Add options docs in the README.md option(VEREIGN_USE_LLD "Use the lld linker" OFF) option(VEREIGN_USE_PRECOMPILED_HEADERS "Use precompiled headers" OFF) option(VEREIGN_USE_TIME_TRACE "Use compilation profiler" OFF) -option(VEREIGN_ENABLE_BENCHMARKING "Enable tests benchmarks" ON) +option(VEREIGN_ENABLE_BENCHMARKING "Enable tests benchmarks" OFF) if (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.0.24215.1") @@ -29,18 +26,25 @@ else() endif() if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") - add_definitions(-D_WIN32_WINNT=0x0601) - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Release>:Release>") - set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>") + # add_definitions(-D_WIN32_WINNT=0x0601) + add_definitions(-D_WIN32_WINNT=0x0A00) + + # set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL$<$<CONFIG:Release>:Release>") + # set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL$<$<CONFIG:Debug>:Debug>") + + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebugDLL") + endif() set(CMAKE_C_FLAGS "/DNDEBUG /DWIN32 /D_WINDOWS /W3") set(CMAKE_CXX_FLAGS "/DNDEBUG /DWIN32 /D_WINDOWS /W3 /GR /EHsc") - set(CMAKE_C_FLAGS_DEBUG "/MTd /Zi /Ob0 /Od /RTC1") - set(CMAKE_CXX_FLAGS_DEBUG "/MTd /Zi /Ob0 /Od /RTC1") + set(CMAKE_C_FLAGS_DEBUG "/MDd /Zi /Ob0 /Od /RTC1") + set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Zi /Ob0 /Od /RTC1") - set(CMAKE_CXX_FLAGS_RELEASE "/Gd /MT /O2 /Oi /Ot /Gy /Zi /GL") - set(CMAKE_C_FLAGS_RELEASE "/Gd /MT /O2 /Oi /Ot /Gy /Zi /GL") + set(CMAKE_CXX_FLAGS_RELEASE "/Gd /MD /O2 /Oi /Ot /Gy /Zi /GL") + set(CMAKE_C_FLAGS_RELEASE "/Gd /MD /O2 /Oi /Ot /Gy /Zi /GL") if (CMAKE_BUILD_TYPE STREQUAL "Release") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF /LTCG") @@ -64,7 +68,7 @@ endif() set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_EXTENSIONS OFF) message("Generator: " "${CMAKE_GENERATOR}") @@ -83,7 +87,6 @@ message("CXX static linker flags: " "${CMAKE_STATIC_LINKER_FLAGS}") string(TOLOWER "${CMAKE_BUILD_TYPE}" _build_type) set(VENDOR_INSTALL_DIR ${CMAKE_SOURCE_DIR}/cmake-install-vendor-${_build_type} CACHE STRING "vendor directory") message(STATUS "Using vendor install dir: ${VENDOR_INSTALL_DIR}") -#set(VENDOR_INSTALL_DIR /home/daniel/workspace/local) set(_cmake_prefix_paths ${VENDOR_INSTALL_DIR} ${VENDOR_INSTALL_DIR}/grpc @@ -98,6 +101,9 @@ include(ProtoGenerate) enable_testing() find_package(fmt 6.2.0 REQUIRED) +if (fmt_FOUND) + get_target_property(fmt_INCLUDE_DIR fmt::fmt INTERFACE_INCLUDE_DIRECTORIES) +endif() set(OPENSSL_USE_STATIC_LIBS ON) set(OPENSSL_ROOT_DIR ${VENDOR_INSTALL_DIR}/boringssl) @@ -109,7 +115,7 @@ find_package( 1.72.0 EXACT REQUIRED - COMPONENTS regex thread system + COMPONENTS regex thread system date_time ) find_package(Protobuf CONFIG REQUIRED) @@ -161,6 +167,7 @@ message(STATUS "summary of build options: EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS_${_build_type}} ${CMAKE_EXE_LINKER_FLAGS} WARNCFLAGS: ${WARNCFLAGS} CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} + CMAKE_MSVC_RUNTIME_LIBRARY: ${CMAKE_MSVC_RUNTIME_LIBRARY} Libs: fmt: ${fmt_FOUND} [${fmt_VERSION}] (DIR='${fmt_DIR}') OpenSSL: ${OpenSSL_FOUND} [${OPENSSL_VERSION}] (LIBS='${OPENSSL_LIBRARIES}') diff --git a/cpp/README.md b/cpp/README.md new file mode 100644 index 0000000000000000000000000000000000000000..46786580c683df123679a7234823655ad1a8ab7f --- /dev/null +++ b/cpp/README.md @@ -0,0 +1,91 @@ +# Vereign C++ Client Library + +## Overview + +Vereign C++ Client Library allows for digitally signing emails and documents, data encryption, +and key-based authentication. + +FIXME: Add more info about the software architecture. + +## Build + +The project uses git submodules for the gRPC protobuf definitions. + +So after cloning run `git submodule update --init`. + +The protobuf definitions and the generated C++ gRPC client/server are located in +[https://code.vereign.com/code/vcl-proto](https://code.vereign.com/code/vcl-proto) project and +are added as submodule at `proto` subdir. + +### Windows + +You need Visual Studio 2019 or alternatively install just the build tools and c++ compiler - go to +[https://visualstudio.microsoft.com/downloads/](https://visualstudio.microsoft.com/downloads/) +then scroll and open `Tools for Visual Studio 2019` and download/install `Visual Studio Build Tools 2019`. +The 2019 build tools include cmake, and currently it is 3.16.19112601-MSVC_2. +They also include Ninja build tool. + +**NOTE: Currently only builds with Ninja are tested under Windows** + +#### Build the third party dependencies + +Go to the `vcl/cpp` directory. + +First configure the cmake build. +Open `x64 Native Tools Command Prompt` it is preconfigured with all the needed tools including cmake +and ninja. + +```shell +> mkdir cmake-build-vendor-debug +> cd cmake-build-vendor-debug +> cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug ../vendor +``` + +Next build the dependencies. +Note that this will take a while, and that it could be slowed a lot by the windows defender. +You can configure the windows defender to the project directory. + +```shell +> ninja +``` + +#### Build the vereign library + +Go to the `vcl/cpp` directory. + +``` +> mkdir cmake-build-debug +> cd cmake-build-debug +> cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug .. +> ninja vereign -v +``` + +The `vereign.dll` will be created inside `cmake-build-debug\bin\Debug` directory. + +### C API Usage + +For C/C++ users, the library has a single header file located in `include\vereign` directory. + +The service is started with `vereign_service_start`. This will start a gRPC server in its own thread(s). +The call returns immediately. + +After the service is started you can use your favorite language generated gRPC client to communicate +with the service. + +Use the `vereign_service_selected_port` function to get the listen port of the gRPC server. + +When you are finished with the service, for example upon shutdown of the application, one must use +the `vereign_service_shutdown` API. It will shutdown the service and free any acquired resources. +Afterwards the returned `vereign_service` pointer from the start call is invalid and must not be used anymore. + +For more detailed info check the source code documentation in the header +[include/vereign/vereign.h](cpp/include/vereign/vereign.h). +You can also look at C++ usage example in the C API integration test +[tests/integration/integration_test.cc](cpp/tests/integration/integration_test.cc). + +The gRPC APIs are located here [https://code.vereign.com/code/vcl-proto/-/tree/master/proto%2Fvereign%2Fclient_library](https://code.vereign.com/code/vcl-proto/-/tree/master/proto%2Fvereign%2Fclient_library). + +FIXME: Add sample integration - for example for C#. + + +TODO: Add more documentation/reference; instructions to generate doxygen reference. diff --git a/cpp/docs/doxygen.config b/cpp/docs/doxygen.config index 89459d394d80b5b0cca47920370d319c67baa7ec..2be52903637d12d6ec4ae7903baa7660b45382de 100644 --- a/cpp/docs/doxygen.config +++ b/cpp/docs/doxygen.config @@ -792,6 +792,7 @@ WARN_LOGFILE = # Note: If this tag is empty the current directory is searched. INPUT = src +INPUT += include # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -816,8 +817,8 @@ INPUT_ENCODING = UTF-8 # *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, # *.f, *.for, *.tcl, *.vhd, *.vhdl, *.ucf and *.qsf. + # *.cc \ FILE_PATTERNS = *.c \ - *.cc \ *.cxx \ *.cpp \ *.c++ \ diff --git a/cpp/include/vereign/vereign.h b/cpp/include/vereign/vereign.h new file mode 100644 index 0000000000000000000000000000000000000000..c450bd819efba614e91f8c0cac23dfbe4c5503a3 --- /dev/null +++ b/cpp/include/vereign/vereign.h @@ -0,0 +1,125 @@ +#ifndef VEREIGN_VEREIGN_H_ +#define VEREIGN_VEREIGN_H_ + +#ifdef _WIN32 + #ifdef WIN_EXPORT + #define PUBLIC_API __declspec(dllexport) + #else + #define PUBLIC_API __declspec(dllimport) + #endif +#else + #define PUBLIC_API +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Error codes. + */ +enum vereign_error_code { + VEREIGN_ERR_INTERNAL = 1, + VEREIGN_ERR_GRPC_BIND_FAILED = 2 +}; + +/** + * Error object used for the error handling of the APIs. + * + * The error has a code and error message, which can be obtained by using the corresponding + * methods ... + */ +typedef struct vereign_error vereign_error; + +/** + * Destroys ::vereign_error object. + * + * Error objects are created by the API calls as result of failures. + * + * It is safe to call it with null err. + * + * @param err The error to destroy. + */ +PUBLIC_API void vereign_error_free(vereign_error* err); + +/** + * Returns error object's code. + * + * @param err The error object. + * @returns the error code. + */ +PUBLIC_API int vereign_error_code(vereign_error* err); + +/** + * Returns error object's message. + * + * @param err The error object. + * @returns the error message. + */ +PUBLIC_API const char* vereign_error_message(vereign_error* err); + +/** + * Provides the gRPC API services. + * + * The object is created with ::vereign_service_start method, and shutdown with + * ::vereign_service_shutdown. + * + * All object's methods are named with prefix `vereign_service_`. + */ +typedef struct vereign_service vereign_service; + +// FIXME: generate SSL keys for gRPC communication. +/** + * Creates ::vereign_service object and starts the gRPC API. + * + * The returned ::vereign_service object must be destroyed with ::vereign_service_shutdown method. + * After successful start, the gRPC bind port can be retrieved with the + * ::vereign_service_selected_port method. This is useful if the `listen_address` is in the form + * "<host>:" (for example "localhost:"), in which case the port is not provided by the user but + * from the OS. + * + * When the gRPC server cannot bind, `err` will have code VEREIGN_ERR_GRPC_BIND_FAILED. + * Any other failures will set `err` with code VEREIGN_ERR_INTERNAL. + * + * **NOTE: On failure the `err` object must be freed with vereign_error_free method.** + * + * @param listen_address gRPC listen address, for example "localhost:". + * @param vereignHost Vereign restapi host. + * @param vereignPort Vereign restapi port - https, 443... + * @param err On failure err is initialized with the reason of the failure, + * otherwise err is set to nullptr. + * @returns vereign_service object if the gRPC is up and running, otherwise returns nullptr. + */ +PUBLIC_API vereign_service* vereign_service_start( + const char* listen_address, + const char* vereign_host, + const char* vereign_port, + // FIXME: public_key must come from a storage internally. + const char* public_key, + vereign_error** err +); + +/** + * Retruns the port number selected by the gRPC server of the service. + * + * Must be called after the service is created with ::vereign_service_start, and before + * it is destroyed with ::vereign_service_shutdown. + * + * @returns the port number selected by the gRPC server of the service. + */ +PUBLIC_API int vereign_service_selected_port(vereign_service* service); + +/** + * Stops and destroys the vereign service. + * + * It is safe to call with null `service`. + * + * @param service The object returned by the ::vereign_service_start function. + */ +PUBLIC_API void vereign_service_shutdown(vereign_service* service); + +#ifdef __cplusplus +}; +#endif + +#endif // VEREIGN_VEREIGN_H_ diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index c986d16357924b039fc541d518c51c45906c7578..555515506f7c1d62b01e54cf2eeaeeb5401e783b 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -2,11 +2,15 @@ if (fmt_FOUND) get_target_property(FMT_INCLUDE_DIR fmt::fmt INTERFACE_INCLUDE_DIRECTORIES) endif() +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + add_definitions(-DNOGDI) +endif() + include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}/src ${VENDOR_INSTALL_DIR}/include - ${VENDOR_INSTALL_DIR}/boost/include + ${Boost_INCLUDE_DIRS} ${FMT_INCLUDE_DIR} ${CMAKE_SOURCE_DIR}/proto/cpp ) @@ -26,10 +30,10 @@ list(APPEND PROTO_SRC ${CMAKE_SOURCE_DIR}/proto/cpp/code.vereign.com/code/viam-apis/passport-generation-agent/api/api.pb.cc ) add_library(vereignproto STATIC ${PROTO_SRC}) +set_property(TARGET vereignproto PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_libraries( vereignproto - fmt::fmt protobuf::libprotobuf OpenSSL::SSL OpenSSL::Crypto @@ -54,12 +58,31 @@ file(GLOB GENERATED_SERVICES_SRC vereign/service/gen/*.cc) list(APPEND VEREIGNLIB_SRC ${GENERATED_SERVICES_SRC}) add_library(vereignlib STATIC ${VEREIGNLIB_SRC}) -target_link_libraries(vereignlib PRIVATE nlohmann_json::nlohmann_json) +set_property(TARGET vereignlib PROPERTY POSITION_INDEPENDENT_CODE ON) +target_link_libraries(vereignlib PRIVATE + nlohmann_json::nlohmann_json +) target_link_libraries(vereignlib PUBLIC vereignproto + fmt::fmt gRPC::grpc++_reflection gRPC::grpc++ + $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time> +) + +add_library(vereign SHARED + vereign/vereign.cc +) +target_include_directories(vereign + PRIVATE ${CMAKE_SOURCE_DIR}/include +) +target_link_libraries(vereign PRIVATE + vereignlib ) +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Set the DLLEXPORT variable to export symbols + target_compile_definitions(vereign PRIVATE WIN_EXPORT) +endif() set(csandbox_sources csandbox.cc @@ -71,7 +94,10 @@ set(csandbox_sources add_executable(csandbox ${csandbox_sources}) target_link_libraries(csandbox - vereignlib + PRIVATE vereignlib + $<$<CXX_COMPILER_ID:MSVC>:Boost::date_time> + # Boost::thread + # vereign # fmt::fmt # Boost::regex # Threads::Threads diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc index 7a1eb95ff82436b8f836f9d8e2bdf1e73651a087..561cf2123f21bd6b5ad1a9b6858d2fb5b2e83a7d 100644 --- a/cpp/src/csandbox.cc +++ b/cpp/src/csandbox.cc @@ -1,6 +1,4 @@ -#include <iostream> auto main(int argc, char* argv[]) -> int { - std::cout << "hello" << std::endl; return 0; } diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc index c89a27433ec2628e1feccfa54030c47cb80b3d70..fd3c911aae22ff35bb513772a27b7d8d3555b985 100644 --- a/cpp/src/vereign/grpc/server.cc +++ b/cpp/src/vereign/grpc/server.cc @@ -32,14 +32,16 @@ public: const std::string& vereignPort, // FIXME: the public key must come from a storage const std::string& publicKey - ) : work_guard_{asio::make_work_guard(ioc_)}, + ) : selected_port_{0}, + work_guard_{asio::make_work_guard(ioc_)}, ssl_context_{asio::ssl::context::tlsv12_client}, client_{std::make_unique<restapi::Client>( ioc_, ssl_context_, vereignHost, vereignPort )}, client_session_{std::make_unique<restapi::ClientSession>( *client_, publicKey - )} + )}, + server_{nullptr} { // FIXME: Verify the remote server's certificate @@ -60,8 +62,8 @@ public: services_registry_.RegisterIntoBuilder(builder); server_ = builder.BuildAndStart(); - if (server_ == nullptr) { - throw std::runtime_error("server start failed"); + if (server_ == nullptr || selected_port_ == 0) { + throw BindError{}; } server_thread_ = std::thread([this]() { diff --git a/cpp/src/vereign/grpc/server.hh b/cpp/src/vereign/grpc/server.hh index d2c4cba704280e820ae024d0108c8f892911af40..bcadb25458827207c43b6a0050c92c5dda9cd0fa 100644 --- a/cpp/src/vereign/grpc/server.hh +++ b/cpp/src/vereign/grpc/server.hh @@ -7,6 +7,16 @@ namespace vereign { namespace grpc { +/** + * BindError is thrown when the Server::Server could not start listening. + */ +class BindError: public virtual std::exception { +public: + auto what() const noexcept -> const char* override { + return "gRPC listen failed"; + } +}; + /** * Server is a grpc server that provides the Vereign services. * @@ -18,9 +28,13 @@ public: /** * Constructs and bootstraps the server. * + * When the gRPC could not start listening a BindError is thrown. + * This can happen for number of reasons like invalid hostname or the listen port is already in use. + * * @param listenAddress gRPC listen address, for example "localhost:". * @param vereignHost Vereign restapi host. * @param vereignPort Vereign restapi port - https, 443... + * @throws BindError when the gRPC server could not start listening. */ explicit Server( const std::string& listenAddress, diff --git a/cpp/src/vereign/restapi/post_result.hh b/cpp/src/vereign/restapi/post_result.hh index 6d31f5bebb217b9c79d28435f5617a56a3607c81..a975f0efdac8f4fb509d45918bc7a5cf3d31cd56 100644 --- a/cpp/src/vereign/restapi/post_result.hh +++ b/cpp/src/vereign/restapi/post_result.hh @@ -12,12 +12,18 @@ struct PostResult { RequestPtr Request; ResponsePtr Response; + PostResult() + : Request{nullptr}, + Response{nullptr} + {} + PostResult(RequestPtr req, ResponsePtr resp) : Request{std::move(req)}, Response{std::move(resp)} {} PostResult(PostResult&& other) = default; + PostResult& operator=(PostResult&& other) = default; PostResult(const PostResult&) = delete; PostResult& operator=(const PostResult&) = delete; diff --git a/cpp/src/vereign/vereign.cc b/cpp/src/vereign/vereign.cc new file mode 100644 index 0000000000000000000000000000000000000000..5bbc7e934fe7b9ff917e4b4b75a4a236c8b671f7 --- /dev/null +++ b/cpp/src/vereign/vereign.cc @@ -0,0 +1,75 @@ +#include <vereign/vereign.h> +#include <vereign/grpc/server.hh> + +struct vereign_error { + int code; + std::string msg; +}; + +void vereign_error_free(vereign_error* err) { + delete err; +} + +auto vereign_error_code(vereign_error* err) -> int { + return err->code; +} + +auto vereign_error_message(vereign_error* err) -> const char* { + return err->msg.data(); +} + +struct vereign_service { + std::unique_ptr<vereign::grpc::Server> impl; +}; + +// FIXME: generate SSL keys for gRPC communication. +auto vereign_service_start( + const char* listen_address, + const char* vereign_host, + const char* vereign_port, + // FIXME: public_key must come from a storage internally. + const char* public_key, + vereign_error** err +) -> vereign_service* { + if (err != nullptr) { + *err = nullptr; + } + + std::unique_ptr<vereign::grpc::Server> serviceImpl; + + try { + serviceImpl = std::make_unique<vereign::grpc::Server>( + listen_address, + vereign_host, + vereign_port, + public_key + ); + + return new vereign_service{std::move(serviceImpl)}; + } catch (const vereign::grpc::BindError& e) { + if (err != nullptr) { + *err = new vereign_error{VEREIGN_ERR_GRPC_BIND_FAILED, e.what()}; + } + } catch (const std::exception& e) { + if (err != nullptr) { + *err = new vereign_error{VEREIGN_ERR_INTERNAL, e.what()}; + } + } + + return nullptr; +} + + +auto vereign_service_selected_port(vereign_service* service) -> int { + return service->impl->SelectedPort(); +} + +void vereign_service_shutdown(vereign_service* service) { + if (service == nullptr) { + return; + } + + service->impl->Shutdown(); + delete service; +} + diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 522c6333935b69525d9e1a2d21b105a610882d5e..62bd6f48b027be1b1605a1f083be7c52d0ecaf00 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -6,3 +6,4 @@ include_directories( add_subdirectory("protobuf") add_subdirectory("vereign") +add_subdirectory("integration") diff --git a/cpp/tests/integration/CMakeLists.txt b/cpp/tests/integration/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..9efb1023b1885e94c729c2d0259cd1963e188273 --- /dev/null +++ b/cpp/tests/integration/CMakeLists.txt @@ -0,0 +1,26 @@ +include_directories( + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/include + # ${VENDOR_INSTALL_DIR}/include + ${CMAKE_SOURCE_DIR}/proto/cpp +) + +list(APPEND tests_src + ../init_tests.cc + + integration_test.cc +) +add_executable(integration_test ${tests_src}) + +target_link_libraries(integration_test + vereign + fmt::fmt + vereignproto + gRPC::grpc++_reflection + gRPC::grpc++ +) + +add_test( + NAME integration_test + COMMAND integration_test +) diff --git a/cpp/tests/integration/integration_test.cc b/cpp/tests/integration/integration_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..ef42c7201cd946c0b613616ab45813340c351dd8 --- /dev/null +++ b/cpp/tests/integration/integration_test.cc @@ -0,0 +1,152 @@ +#include <vereign/vereign.h> +#include <util/env.hh> +#include <vereign/client_library/types.gen.pb.h> +#include <vereign/client_library/passport_api.gen.grpc.pb.h> +#include <vereign/core/scope_guard.hh> +#include <grpcpp/create_channel.h> + +#include <catch2/catch.hpp> + +TEST_CASE("C API integration", "[.integration]") { + auto publicKey = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + // start the service + vereign_error* err = nullptr; + auto service = vereign_service_start( + "localhost:", + host.data(), + port.data(), + publicKey.data(), + &err + ); + CHECK(service != nullptr); + CHECK(err == nullptr); + auto on_exit = vereign::core::MakeScopeGuard([service] { + vereign_service_shutdown(service); + }); + + int listen_port = vereign_service_selected_port(service); + + // start using the gRPC API with a C++ gRPC client. + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(listen_port), + ::grpc::InsecureChannelCredentials() + ); + + auto client = vereign::client_library::PassportAPI::NewStub(channel); + + vereign::client_library::ListPassportsForm req; + vereign::client_library::ListPassportsFormResponse resp; + ::grpc::ClientContext ctx; + auto status = client->ListPassports(&ctx, req, &resp); + + // std::cout << vereign::test::ProtobufToJson(resp) << std::endl; + + REQUIRE(status.error_message() == ""); + REQUIRE(resp.error() == ""); + CHECK(resp.status() == "OK"); + CHECK(resp.code() == "200"); + CHECK(resp.data().size() > 0); + for (auto& passport : resp.data()) { + CHECK(passport.uuid().size() == 36); + } + + req.Clear(); + resp.Clear(); + ::grpc::ClientContext manually_ctx; + status = client->ListPassportsManually(&manually_ctx, req, &resp); + + // std::cout << vereign::test::ProtobufToJson(resp) << std::endl; + + REQUIRE(status.error_message() == ""); + REQUIRE(resp.error() == ""); + CHECK(resp.status() == "OK"); + CHECK(resp.code() == "200"); + CHECK(resp.data().size() > 0); + for (auto& passport : resp.data()) { + CHECK(passport.uuid().size() == 36); + } + + vereign::client_library::GetInteractionsForm getInterReq; + getInterReq.set_uuid(resp.data().at(0).uuid()); + vereign::client_library::GetInteractionsFormResponse getInterResp; + ::grpc::ClientContext getInterCtx; + status = client->GetInteractions(&getInterCtx, getInterReq, &getInterResp); + CHECK(status.error_message() == ""); + CHECK(getInterResp.error() == ""); + CHECK(getInterResp.status() == "OK"); + CHECK(getInterResp.code() == "200"); + for (auto& interaction : getInterResp.data()) { + CHECK(interaction.subject().size() > 0); + CHECK(interaction.passport().size() == 36); + } + + // std::cout << vereign::test::ProtobufToJson(getDIDsResp) << std::endl; + + grpc_shutdown(); + google::protobuf::ShutdownProtobufLibrary(); +} + +TEST_CASE("vereign_service_start") { + SECTION("success") { + vereign_error* err = nullptr; + auto service = vereign_service_start( + "localhost:", + "", + "", + "", + &err + ); + + CHECK(service != nullptr); + CHECK(err == nullptr); + + vereign_service_shutdown(service); + } + + SECTION("invalid listen address") { + vereign_error* err = nullptr; + auto service = vereign_service_start( + "##$$", + "", + "", + "", + &err + ); + + CHECK(service == nullptr); + CHECK(err != nullptr); + CHECK(vereign_error_code(err) == VEREIGN_ERR_GRPC_BIND_FAILED); + std::string error_message = vereign_error_message(err); + CHECK(error_message == "gRPC listen failed"); + + vereign_error_free(err); + vereign_service_shutdown(service); + } +} + +TEST_CASE("vereign_service_selected_port") { + vereign_error* err = nullptr; + auto service = vereign_service_start( + "localhost:", + "", + "", + "", + &err + ); + + REQUIRE(service != nullptr); + REQUIRE(err == nullptr); + + auto port = vereign_service_selected_port(service); + CHECK((port > 0 && port <= 65535)); + + vereign_service_shutdown(service); +} + +TEST_CASE("vereign_service_shutdown") { + // shutdown must not fail with nullptr + vereign_service_shutdown(nullptr); +} diff --git a/cpp/tests/util/protobuf.cc b/cpp/tests/util/protobuf.cc index 544c5770d1b70a5a06192488cdcbc85adfd0731b..05e1c791a91878f1d09a4c0068227de89e7937e1 100644 --- a/cpp/tests/util/protobuf.cc +++ b/cpp/tests/util/protobuf.cc @@ -1,8 +1,7 @@ -#include "vereign/grpc/json/encoder.hh" +#include <vereign/grpc/json/encoder.hh> #include <util/protobuf.hh> #include <google/protobuf/util/json_util.h> -#include <fmt/core.h> #include <catch2/catch.hpp> diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt index 4deb797f1635045a7c356cbcfa1f0fcd8eae940c..2c2b6fce316e7721ed05a80e53330200081a4486 100644 --- a/cpp/tests/vereign/CMakeLists.txt +++ b/cpp/tests/vereign/CMakeLists.txt @@ -1,12 +1,15 @@ +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + add_definitions(-DNOGDI) +endif() include_directories( ${CMAKE_SOURCE_DIR}/src ${VENDOR_INSTALL_DIR}/include ${CMAKE_SOURCE_DIR}/proto/cpp + ${Boost_INCLUDE_DIRS} ) - list(APPEND tests_src init_tests.cc ../util/protobuf.cc diff --git a/cpp/vendor/CMakeLists.txt b/cpp/vendor/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..7d4cb218c8cc4ae5f18c2a2d6a792071df35ab0d --- /dev/null +++ b/cpp/vendor/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required (VERSION 3.16.5) + +project (vereign-vendor) + +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + add_definitions(-D_WIN32_WINNT=0x0601) + + set(CMAKE_C_FLAGS "/DNDEBUG /DWIN32 /D_WINDOWS /W3") + set(CMAKE_CXX_FLAGS "/DNDEBUG /DWIN32 /D_WINDOWS /W3 /GR /EHsc") + + set(CMAKE_C_FLAGS_DEBUG "/MDd /Zi /Ob0 /Od /RTC1") + set(CMAKE_CXX_FLAGS_DEBUG "/MDd /Zi /Ob0 /Od /RTC1") + + set(CMAKE_CXX_FLAGS_RELEASE "/Gd /MD /O2 /Oi /Ot /Gy /Zi /GL") + set(CMAKE_C_FLAGS_RELEASE "/Gd /MD /O2 /Oi /Ot /Gy /Zi /GL") + + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Release>:Release>") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL") + + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL") + if (CMAKE_BUILD_TYPE STREQUAL "Debug") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebugDLL") + endif() +endif() + +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(CMAKE_C_FLAGS_RELEASE "-g -O3 -Wall -Wextra -pedantic") + set(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Wextra -pedantic") + set(CMAKE_CXX_FLAGS_RELEASE "-g -O3 -Wall -Wextra -pedantic") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -pedantic") +endif() + +if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + set(CMAKE_C_FLAGS_RELEASE "-g -O3 -Wall -Wextra -pedantic") + set(CMAKE_C_FLAGS_DEBUG "-g -O0 -Wall -Wextra -pedantic") + set(CMAKE_CXX_FLAGS_RELEASE "-g -O3 -Wall -Wextra -pedantic") + set(CMAKE_CXX_FLAGS_DEBUG "-g -O0 -Wall -Wextra -pedantic") +endif() + +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_EXTENSIONS OFF) + +string(TOLOWER "${CMAKE_BUILD_TYPE}" _build_type) +set(VENDOR_INSTALL_DIR ${CMAKE_SOURCE_DIR}/../cmake-install-vendor-${_build_type} CACHE STRING "vendor directory") +set(CMAKE_PREFIX_PATH ${VENDOR_INSTALL_DIR} CACHE STRING "") +option(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH NO) + +include(FetchContent) +include(ExternalProject) + +include(fmt.cmake) +include(boring_ssl.cmake) +include(boost.cmake) +include(grpc.cmake) +include(nlohmann.cmake) + +string(TOUPPER "${CMAKE_BUILD_TYPE}" _build_type) +message(STATUS "Summary: + + Vendor install dir: ${VENDOR_INSTALL_DIR} + Compiler: + Build type: ${CMAKE_BUILD_TYPE} + C compiler: ${CMAKE_C_COMPILER} + CFLAGS: ${CMAKE_C_FLAGS_${_build_type}} ${CMAKE_C_FLAGS} + C++ compiler: ${CMAKE_CXX_COMPILER} + CXXFLAGS: ${CMAKE_CXX_FLAGS_${_build_type}} ${CMAKE_CXX_FLAGS} + WARNCFLAGS: ${WARNCFLAGS} + CXX1XCXXFLAGS: ${CXX1XCXXFLAGS} + CMAKE_MSVC_RUNTIME_LIBRARY: ${CMAKE_MSVC_RUNTIME_LIBRARY} +") diff --git a/cpp/vendor/boost.cmake b/cpp/vendor/boost.cmake new file mode 100644 index 0000000000000000000000000000000000000000..430902d781c72e6b107b4709b13a84f7160a1220 --- /dev/null +++ b/cpp/vendor/boost.cmake @@ -0,0 +1,38 @@ +include(ExternalProject) + +set(_boost_libs regex system thread date_time) + +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + list(TRANSFORM _boost_libs PREPEND --with-) + + set(_configure_command <SOURCE_DIR>/bootstrap.bat) + set(_build_command <SOURCE_DIR>/b2 install --prefix=<INSTALL_DIR> ${_boost_libs} link=static) + set(_install_command "") +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(_boost_toolset clang) + set(_boost_cxx_flags "-std=c++${CMAKE_CXX_STANDARD} -fPIC") + set(_boost_c_flags "-fPIC") + string(REPLACE ";" "," _boost_libs "${_boost_libs}") + + set(_configure_command <SOURCE_DIR>/bootstrap.sh --prefix=<INSTALL_DIR> --with-toolset=${_boost_toolset} --with-libraries=${_boost_libs}) + set(_build_command <SOURCE_DIR>/b2 toolset=${_boost_toolset} cxxflags=${_boost_cxx_flags} cflags=${_boost_c_flags}) + set(_install_command <SOURCE_DIR>/b2 install) +endif() + +ExternalProject_Add(boostlib + PREFIX boost + URL https://dl.bintray.com/boostorg/release/1.72.0/source/boost_1_72_0.zip + URL_HASH SHA256=8c20440aaba21dd963c0f7149517445f50c62ce4eb689df2b5544cc89e6e621e + BUILD_IN_SOURCE ON + INSTALL_DIR ${VENDOR_INSTALL_DIR}/boost + + USES_TERMINAL_DOWNLOAD ON + USES_TERMINAL_UPDATE ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + + CONFIGURE_COMMAND "${_configure_command}" + BUILD_COMMAND "${_build_command}" + INSTALL_COMMAND "${_install_command}" +) diff --git a/cpp/vendor/boring_ssl.cmake b/cpp/vendor/boring_ssl.cmake new file mode 100644 index 0000000000000000000000000000000000000000..91cfaa725dd36053bec5ea75a9f22dfb496e5ed6 --- /dev/null +++ b/cpp/vendor/boring_ssl.cmake @@ -0,0 +1,44 @@ + +if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(_install_command + ${CMAKE_COMMAND} -E copy crypto.lib <INSTALL_DIR>/lib/crypto.lib + COMMAND ${CMAKE_COMMAND} -E copy ssl.lib <INSTALL_DIR>/lib/ssl.lib + COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/src/include <INSTALL_DIR>/include + ) +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set(_install_command + ${CMAKE_COMMAND} -E copy libcrypto.a <INSTALL_DIR>/lib/libcrypto.a + COMMAND ${CMAKE_COMMAND} -E copy libssl.a <INSTALL_DIR>/lib/libssl.a + COMMAND ${CMAKE_COMMAND} -E copy_directory <SOURCE_DIR>/src/include <INSTALL_DIR>/include + ) +endif() + +ExternalProject_Add(boringssllib + PREFIX boringssl + GIT_REPOSITORY git@github.com:google/boringssl.git + GIT_TAG 1c2769383f027befac5b75b6cedd25daf3bf4dcf + INSTALL_DIR ${VENDOR_INSTALL_DIR}/boringssl + + USES_TERMINAL_DOWNLOAD ON + USES_TERMINAL_UPDATE ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + + CMAKE_CACHE_ARGS + -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} + -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=${CMAKE_MSVC_RUNTIME_LIBRARY} + -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} + -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} + -DCMAKE_C_FLAGS_DEBUG:STRING=${CMAKE_C_FLAGS_DEBUG} + -DCMAKE_CXX_FLAGS_DEBUG:STRING=${CMAKE_CXX_FLAGS_DEBUG} + -DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE} + -DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE} + -DBUILD_SHARED_LIBS:BOOL=OFF + -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON + + INSTALL_COMMAND "${_install_command}" +) diff --git a/cpp/vendor/fmt.cmake b/cpp/vendor/fmt.cmake new file mode 100644 index 0000000000000000000000000000000000000000..55359ac9333f8f58e3ce1a95d03b2165913ecb7e --- /dev/null +++ b/cpp/vendor/fmt.cmake @@ -0,0 +1,28 @@ + +ExternalProject_Add(fmtlib + PREFIX fmt + GIT_REPOSITORY git@github.com:fmtlib/fmt.git + GIT_TAG 6.2.0 + INSTALL_DIR ${VENDOR_INSTALL_DIR}/fmt + + USES_TERMINAL_DOWNLOAD ON + USES_TERMINAL_UPDATE ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + + CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=${CMAKE_MSVC_RUNTIME_LIBRARY} + -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} + -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} + -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} + -DCMAKE_C_FLAGS_DEBUG:STRING=${CMAKE_C_FLAGS_DEBUG} + -DCMAKE_CXX_FLAGS_DEBUG:STRING=${CMAKE_CXX_FLAGS_DEBUG} + -DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE} + -DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE} + -DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=ON + -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> + -DFMT_TEST:BOOL=OFF +) diff --git a/cpp/vendor/grpc.cmake b/cpp/vendor/grpc.cmake new file mode 100644 index 0000000000000000000000000000000000000000..a33a809c38cf41bcf9c0136dc51fab3f9bb4176a --- /dev/null +++ b/cpp/vendor/grpc.cmake @@ -0,0 +1,33 @@ + +ExternalProject_Add(grpclib + PREFIX grpc + GIT_REPOSITORY git@github.com:grpc/grpc.git + GIT_TAG v1.28.1 + GIT_SHALLOW ON + INSTALL_DIR ${VENDOR_INSTALL_DIR}/grpc + DEPENDS boringssllib + + USES_TERMINAL_DOWNLOAD ON + USES_TERMINAL_UPDATE ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + + CMAKE_CACHE_ARGS + -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} + -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=${CMAKE_MSVC_RUNTIME_LIBRARY} + -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} + -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} + -DCMAKE_C_FLAGS_DEBUG:STRING=${CMAKE_C_FLAGS_DEBUG} + -DCMAKE_CXX_FLAGS_DEBUG:STRING=${CMAKE_CXX_FLAGS_DEBUG} + -DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE} + -DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE} + -DBUILD_SHARED_LIBS:BOOL=OFF + -DgRPC_SSL_PROVIDER:STRING=package + -DOPENSSL_USE_STATIC_LIBS:BOOL=ON + -DOPENSSL_ROOT_DIR:STRING=${VENDOR_INSTALL_DIR}/boringssl + -DgRPC_INSTALL:BOOL=ON +) diff --git a/cpp/vendor/nlohmann.cmake b/cpp/vendor/nlohmann.cmake new file mode 100644 index 0000000000000000000000000000000000000000..93272436440658733a42926a90b4cc156767bae6 --- /dev/null +++ b/cpp/vendor/nlohmann.cmake @@ -0,0 +1,27 @@ + +ExternalProject_Add(nlohmannlib + PREFIX nlohmann + GIT_REPOSITORY git@github.com:nlohmann/json.git + GIT_TAG v3.7.3 + INSTALL_DIR ${VENDOR_INSTALL_DIR}/nlohmann + + USES_TERMINAL_DOWNLOAD ON + USES_TERMINAL_UPDATE ON + USES_TERMINAL_CONFIGURE ON + USES_TERMINAL_BUILD ON + USES_TERMINAL_INSTALL ON + + CMAKE_CACHE_ARGS + -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE} + -DCMAKE_MSVC_RUNTIME_LIBRARY:STRING=${CMAKE_MSVC_RUNTIME_LIBRARY} + -DCMAKE_C_COMPILER:STRING=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER:STRING=${CMAKE_CXX_COMPILER} + -DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} + -DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} + -DCMAKE_C_FLAGS_DEBUG:STRING=${CMAKE_C_FLAGS_DEBUG} + -DCMAKE_CXX_FLAGS_DEBUG:STRING=${CMAKE_CXX_FLAGS_DEBUG} + -DCMAKE_C_FLAGS_RELEASE:STRING=${CMAKE_C_FLAGS_RELEASE} + -DCMAKE_CXX_FLAGS_RELEASE:STRING=${CMAKE_CXX_FLAGS_RELEASE} + -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> + -DJSON_BuildTests:BOOL=OFF +)