diff --git a/build_scripts/OBS b/build_scripts/OBS index c064abd74..879d5dfe8 160000 --- a/build_scripts/OBS +++ b/build_scripts/OBS @@ -1 +1 @@ -Subproject commit c064abd74b27e1cc440917e9dbac800316bb8470 +Subproject commit 879d5dfe8dcd8995be753120cf1b8bab4dd2ec82 diff --git a/libretroshare/src/jsonapi/jsonapi.cpp b/libretroshare/src/jsonapi/jsonapi.cpp index 3888117a5..0c180ce52 100644 --- a/libretroshare/src/jsonapi/jsonapi.cpp +++ b/libretroshare/src/jsonapi/jsonapi.cpp @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see * - * SPDX-FileCopyrightText: 2004-2019 RetroShare Team + * SPDX-FileCopyrightText: 2004-2020 RetroShare Team * SPDX-License-Identifier: AGPL-3.0-only */ @@ -73,6 +73,8 @@ JsonApiServer::corsOptionsHeaders = { "Content-Length", "0" } }; +/* static */ const RsJsonApiErrorCategory RsJsonApiErrorCategory::instance; + #define INITIALIZE_API_CALL_JSON_CONTEXT \ RsGenericSerializer::SerializeContext cReq( \ nullptr, 0, \ @@ -124,32 +126,45 @@ JsonApiServer::corsOptionsHeaders = return false; } +void JsonApiServer::unProtectedRestart() +{ + /* Extremely sensitive stuff! + * Make sure you read documentation in header before changing or use!! */ + + fullstop(); + RsThread::start("JSON API Server"); +} + bool RsJsonApi::parseToken( const std::string& clear_token, std::string& user,std::string& passwd ) { - uint32_t colonIndex = 0; - uint32_t colonCounter = 0; + auto colonIndex = std::string::npos; const auto tkLen = clear_token.length(); - for(uint32_t i=0; i < tkLen && colonCounter < 2; ++i) - if(clear_token[i] == ':') { ++colonCounter; colonIndex = i; } - else if(!librs::util::is_alphanumeric(clear_token[i])) return false; + for(uint32_t i=0; i < tkLen; ++i) + if(clear_token[i] == ':') { colonIndex = i; break; } - if(colonCounter != 1) return false; + user = clear_token.substr(0, colonIndex); - user = clear_token.substr(0, colonIndex); - passwd = clear_token.substr(colonIndex + 1); + if(colonIndex < tkLen) + passwd = clear_token.substr(colonIndex + 1); return true; } - JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), - mService(std::make_shared()), - mServiceMutex("JsonApiServer restbed ptr"), + mService(nullptr), mListeningPort(RsJsonApi::DEFAULT_PORT), - mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS) + mBindingAddress(RsJsonApi::DEFAULT_BINDING_ADDRESS), + mRestartReqTS(0) { +#if defined(RS_THREAD_FORCE_STOP) && defined(RS_JSONAPI_DEBUG_SERVICE_STOP) + /* When called in bursts it seems that Restbed::Service::stop() doesn't + * always does the job, to debug those cases it has been useful to ask + * RsThread to force it to stop for us. */ + RsThread::setStopTimeout(10); +#endif + registerHandler("/rsLoginHelper/createLocation", [this](const std::shared_ptr session) { @@ -248,7 +263,13 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), { INITIALIZE_API_CALL_JSON_CONTEXT; DEFAULT_API_CALL_JSON_RETURN(rb::OK); - rsControl->rsGlobalShutDown(); + + /* Wrap inside RsThread::async because this call + * RsThread::fullstop() also on JSON API server thread. + * Calling RsThread::fullstop() from it's own thread should never + * happen and if it happens an error message is printed + * accordingly by RsThread::fullstop() */ + RsThread::async([](){ rsControl->rsGlobalShutDown(); }); } ); }, true); @@ -388,6 +409,41 @@ JsonApiServer::JsonApiServer(): configMutex("JsonApiServer config"), session->yield(message.str()); } ); }, true); + + registerHandler("/rsJsonApi/restart", + [this](const std::shared_ptr session) + { + auto reqSize = session->get_request()->get_header("Content-Length", 0); + session->fetch( static_cast(reqSize), [this]( + const std::shared_ptr session, + const rb::Bytes& body ) + { + INITIALIZE_API_CALL_JSON_CONTEXT; + + std::error_condition retval; + + const auto now = time(nullptr); + if(mRestartReqTS.exchange(now) + RESTART_BURST_PROTECTION > now) + retval = RsJsonApiErrorNum::NOT_A_MACHINE_GUN; + + // serialize out parameters and return value to JSON + { + RsGenericSerializer::SerializeContext& ctx(cAns); + RsGenericSerializer::SerializeJob j(RsGenericSerializer::TO_JSON); + RS_SERIAL_PROCESS(retval); + } + + DEFAULT_API_CALL_JSON_RETURN(rb::OK); + + /* Wrap inside RsThread::async because this call fullstop() on + * JSON API server thread. + * Calling RsThread::fullstop() from it's own thread should never + * happen and if it happens an error message is printed + * accordingly by RsThread::fullstop() */ + if(!retval) RsThread::async([this](){ unProtectedRestart(); }); + } ); + }, true); + // Generated at compile time #include "jsonapi-wrappers.inl" } @@ -405,10 +461,22 @@ void JsonApiServer::registerHandler( if(requiresAutentication) resource->set_authentication_handler( - [this]( + [this, path]( const std::shared_ptr session, const std::function)>& callback ) { + const auto authFail = + [&path, &session](int status) -> RsWarn::stream_type& + { + /* Capture session by reference as it is cheaper then copying + * shared_ptr by value which is not needed in this case */ + + session->close(status, corsOptionsHeaders); + return RsWarn() << "JsonApiServer authentication handler " + "blocked an attempt to call JSON API " + "authenticated method: " << path; + }; + if(session->get_request()->get_method() == "OPTIONS") { callback(session); @@ -417,7 +485,8 @@ void JsonApiServer::registerHandler( if(!rsLoginHelper->isLoggedIn()) { - session->close(rb::CONFLICT, corsOptionsHeaders); + authFail(rb::CONFLICT) << " before RetroShare login" + << std::endl; return; } @@ -429,15 +498,24 @@ void JsonApiServer::registerHandler( if(authToken != "Basic") { - session->close(rb::UNAUTHORIZED, corsOptionsHeaders); + authFail(rb::UNAUTHORIZED) + << " with wrong Authorization header: " + << authHeader.str() << std::endl; return; } std::getline(authHeader, authToken, ' '); authToken = decodeToken(authToken); - if(isAuthTokenValid(authToken)) callback(session); - else session->close(rb::UNAUTHORIZED, corsOptionsHeaders); + std::error_condition ec; + if(isAuthTokenValid(authToken, ec)) callback(session); + else + { + std::string tUser; + parseToken(authToken, tUser, RS_DEFAULT_STORAGE_PARAM(std::string)); + authFail(rb::UNAUTHORIZED) + << " user: " << tUser << ec << std::endl; + } } ); mResources.push_back(resource); @@ -447,24 +525,62 @@ void JsonApiServer::setNewAccessRequestCallback( const std::function& callback ) { mNewAccessRequestCallback = callback; } -bool JsonApiServer::requestNewTokenAutorization( +/*static*/ std::error_condition JsonApiServer::badApiCredientalsFormat( const std::string& user, const std::string& passwd ) { - if(rsLoginHelper->isLoggedIn() && mNewAccessRequestCallback(user, passwd)) - return authorizeUser(user, passwd); + if(user.find(':') < std::string::npos) + return RsJsonApiErrorNum::API_USER_CONTAIN_COLON; - return false; + if(user.empty()) + RsWarn() << __PRETTY_FUNCTION__ << " User is empty, are you sure " + << "this what you wanted?" << std::endl; + + if(passwd.empty()) + RsWarn() << __PRETTY_FUNCTION__ << " Password is empty, are you sure " + << "this what you wanted?" << std::endl; + + return std::error_condition(); } -bool JsonApiServer::isAuthTokenValid(const std::string& token) +std::error_condition JsonApiServer::requestNewTokenAutorization( + const std::string& user, const std::string& passwd ) +{ + auto ec = badApiCredientalsFormat(user, passwd); + if(ec) return ec; + + if(!rsLoginHelper->isLoggedIn()) + return RsJsonApiErrorNum::CANNOT_EXECUTE_BEFORE_RS_LOGIN; + + if(mNewAccessRequestCallback(user, passwd)) + return authorizeUser(user, passwd); + + return RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED; +} + +bool JsonApiServer::isAuthTokenValid( + const std::string& token, std::error_condition& error ) { RS_STACK_MUTEX(configMutex); + const auto failure = [&error](RsJsonApiErrorNum e) -> bool + { + error = e; + return false; + }; + + const auto success = [&error]() + { + error.clear(); + return true; + }; + std::string user,passwd; - if(!parseToken(token,user,passwd)) return false; + if(!parseToken(token, user, passwd)) + return failure(RsJsonApiErrorNum::TOKEN_FORMAT_INVALID); auto it = mAuthTokenStorage.mAuthorizedTokens.find(user); - if(it == mAuthTokenStorage.mAuthorizedTokens.end()) return false; + if(it == mAuthTokenStorage.mAuthorizedTokens.end()) + return failure(RsJsonApiErrorNum::UNKNOWN_API_USER); // attempt avoiding +else CRYPTO_memcmp+ being optimized away int noOptimiz = 1; @@ -476,12 +592,16 @@ bool JsonApiServer::isAuthTokenValid(const std::string& token) if( passwd.size() == it->second.size() && ( noOptimiz = CRYPTO_memcmp( passwd.data(), it->second.data(), it->second.size() ) ) == 0 ) - return true; + return success(); // Make token size guessing harder else noOptimiz = CRYPTO_memcmp(passwd.data(), passwd.data(), passwd.size()); - // attempt avoiding +else CRYPTO_memcmp+ being optimized away - return static_cast(noOptimiz) + 1 == 0; + /* At this point we are sure password is wrong, and one could think to + * plainly `return false` still this ugly and apparently unuseful extra + * calculation is here to avoid `else CRYPTO_memcmp` being optimized away, + * so a pontential attacker cannot guess password size based on timing */ + return static_cast(noOptimiz) + 1 == 0 ? + success() : failure(RsJsonApiErrorNum::WRONG_API_PASSWORD); } std::map JsonApiServer::getAuthorizedTokens() @@ -509,22 +629,11 @@ void JsonApiServer::connectToConfigManager(p3ConfigMgr& cfgmgr) loadConfiguration(hash); } -bool JsonApiServer::authorizeUser( +std::error_condition JsonApiServer::authorizeUser( const std::string& user, const std::string& passwd ) { - if(!librs::util::is_alphanumeric(user)) - { - RsErr() << __PRETTY_FUNCTION__ << " User name is not alphanumeric" - << std::endl; - return false; - } - - if(passwd.empty()) - { - RsWarn() << __PRETTY_FUNCTION__ << " Password is empty, are you sure " - << "this what you wanted?" << std::endl; - return false; - } + auto ec = badApiCredientalsFormat(user, passwd); + if(ec) return ec; RS_STACK_MUTEX(configMutex); @@ -534,7 +643,7 @@ bool JsonApiServer::authorizeUser( p = passwd; IndicateConfigChanged(); } - return true; + return ec; } /*static*/ std::string JsonApiServer::decodeToken(const std::string& radix64_token) @@ -600,21 +709,21 @@ std::vector > JsonApiServer::getResources() const return tab; } -void JsonApiServer::restart() +std::error_condition JsonApiServer::restart() { - /* It is important to wrap into async(...) because fullstop() method can't - * be called from same thread of execution hence from JSON API thread! */ - RsThread::async([this]() - { - fullstop(); - RsThread::start("JSON API Server"); - }); + const auto now = time(nullptr); + if(mRestartReqTS.exchange(now) + RESTART_BURST_PROTECTION > now) + return RsJsonApiErrorNum::NOT_A_MACHINE_GUN; + + unProtectedRestart(); + return std::error_condition(); } void JsonApiServer::onStopRequested() { - RS_STACK_MUTEX(mServiceMutex); - mService->stop(); + auto tService = std::atomic_exchange( + &mService, std::shared_ptr(nullptr) ); + if(tService) tService->stop(); } uint16_t JsonApiServer::listeningPort() const { return mListeningPort; } @@ -630,14 +739,9 @@ void JsonApiServer::run() settings->set_bind_address(mBindingAddress); settings->set_default_header("Connection", "close"); - /* re-allocating mService is important because it deletes the existing - * service and therefore leaves the listening port open */ - { - RS_STACK_MUTEX(mServiceMutex); - mService = std::make_shared(); - } + auto tService = std::make_shared(); - for(auto& r: getResources()) mService->publish(r); + for(auto& r: getResources()) tService->publish(r); try { @@ -645,7 +749,20 @@ void JsonApiServer::run() .setPort(mListeningPort); RsInfo() << __PRETTY_FUNCTION__ << " JSON API server listening on " << apiUrl.toString() << std::endl; - mService->start(settings); + + /* re-allocating mService is important because it deletes the existing + * service and therefore leaves the listening port open */ + auto tExpected = std::shared_ptr(nullptr); + if(atomic_compare_exchange_strong(&mService, &tExpected, tService)) + tService->start(settings); + else + { + RsErr() << __PRETTY_FUNCTION__ << " mService was expected to be " + << " null, instead we got: " << tExpected + << " something wrong happened JsonApiServer won't start" + << std::endl; + print_stacktrace(); + } } catch(std::exception& e) { @@ -668,3 +785,19 @@ void JsonApiServer::run() extra = RS_EXTRA_VERSION; human = RS_HUMAN_READABLE_VERSION; } + +std::error_condition RsJsonApiErrorCategory::default_error_condition(int ev) const noexcept +{ + switch(static_cast(ev)) + { + case RsJsonApiErrorNum::TOKEN_FORMAT_INVALID: // fallthrough + case RsJsonApiErrorNum::UNKNOWN_API_USER: // fallthrough + case RsJsonApiErrorNum::WRONG_API_PASSWORD: // fallthrough + case RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED: + return std::errc::permission_denied; + case RsJsonApiErrorNum::API_USER_CONTAIN_COLON: + return std::errc::invalid_argument; + default: + return std::error_condition(ev, *this); + } +} diff --git a/libretroshare/src/jsonapi/jsonapi.h b/libretroshare/src/jsonapi/jsonapi.h index 803be8459..70cc5e309 100644 --- a/libretroshare/src/jsonapi/jsonapi.h +++ b/libretroshare/src/jsonapi/jsonapi.h @@ -30,6 +30,7 @@ #include #include #include +#include #include "util/rsthreads.h" #include "pqi/p3cfgmgr.h" @@ -40,6 +41,7 @@ namespace rb = restbed; +/** Interface to provide addotional resources to JsonApiServer */ class JsonApiResourceProvider { public: @@ -66,7 +68,7 @@ public: void fullstop() override { RsThread::fullstop(); } /// @see RsJsonApi - void restart() override; + std::error_condition restart() override; /// @see RsJsonApi void askForStop() override { RsThread::askForStop(); } @@ -90,9 +92,8 @@ public: void connectToConfigManager(p3ConfigMgr& cfgmgr) override; /// @see RsJsonApi - virtual bool authorizeUser( - const std::string& alphanumeric_user, - const std::string& alphanumeric_passwd ) override; + virtual std::error_condition authorizeUser( + const std::string& user, const std::string& passwd ) override; /// @see RsJsonApi std::map getAuthorizedTokens() override; @@ -101,10 +102,13 @@ public: bool revokeAuthToken(const std::string& user) override; /// @see RsJsonApi - bool isAuthTokenValid(const std::string& token) override; + bool isAuthTokenValid( + const std::string& token, + std::error_condition& error = RS_DEFAULT_STORAGE_PARAM(std::error_condition) + ) override; /// @see RsJsonAPI - bool requestNewTokenAutorization( + std::error_condition requestNewTokenAutorization( const std::string& user, const std::string& password ) override; /// @see RsJsonApi @@ -147,6 +151,11 @@ protected: /// @see RsThread void onStopRequested() override; + static const RsJsonApiErrorCategory sErrorCategory; + + static std::error_condition badApiCredientalsFormat( + const std::string& user, const std::string& passwd ); + private: /// @see RsThread void run() override; @@ -195,13 +204,33 @@ private: std::reference_wrapper, std::less > mResourceProviders; + /** + * This pointer should be accessed via std::atomic_* operations, up until + * now only very critical operations like reallocation, are done that way, + * but this is not still 100% thread safe, but seems to handle all of the + * test cases (no crash, no deadlock), once we switch to C++20 we shoud + * change this into std::atomic> which + * will automatically handle atomic access properly all the times + */ std::shared_ptr mService; - /** Protect service only during very critical operation like resetting the - * pointer, still not 100% thread safe, but hopefully we can avoid - * crashes/freeze with this */ - RsMutex mServiceMutex; uint16_t mListeningPort; std::string mBindingAddress; + + /// @see unProtectedRestart() + std::atomic mRestartReqTS; + + /// @see unProtectedRestart() + constexpr static rstime_t RESTART_BURST_PROTECTION = 7; + + /** It is very important to protect this method from being called in bursts, + * because Restbed::Service::stop() together with + * Restbed::Service::start(...), which are called internally, silently fails + * if combined in bursts, probably because they have to deal with + * listening/releasing TCP port. + * @see JsonApiServer::restart() and @see JsonApiServer::JsonApiServer() + * implementation to understand how correctly use this. + */ + void unProtectedRestart(); }; diff --git a/libretroshare/src/retroshare/rsjsonapi.h b/libretroshare/src/retroshare/rsjsonapi.h index 251310aaa..98b052d58 100644 --- a/libretroshare/src/retroshare/rsjsonapi.h +++ b/libretroshare/src/retroshare/rsjsonapi.h @@ -1,8 +1,9 @@ /* * RetroShare JSON API public header * - * Copyright (C) 2018-2019 Gioacchino Mazzurco + * Copyright (C) 2018-2020 Gioacchino Mazzurco * Copyright (C) 2019 Cyril Soler + * Copyright (C) 2020 Asociación Civil Altermundi * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free @@ -25,9 +26,11 @@ #include #include +#include +#include + +#include "util/rsmemory.h" -class p3ConfigMgr; -class JsonApiResourceProvider; class RsJsonApi; /** @@ -36,6 +39,71 @@ class RsJsonApi; */ extern RsJsonApi* rsJsonApi; +enum class RsJsonApiErrorNum : int32_t +{ + TOKEN_FORMAT_INVALID = 2004, + UNKNOWN_API_USER = 2005, + WRONG_API_PASSWORD = 2006, + API_USER_CONTAIN_COLON = 2007, + AUTHORIZATION_REQUEST_DENIED = 2008, + CANNOT_EXECUTE_BEFORE_RS_LOGIN = 2009, + NOT_A_MACHINE_GUN = 2010 +}; + +struct RsJsonApiErrorCategory: std::error_category +{ + const char* name() const noexcept override + { return "RetroShare JSON API"; } + + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case RsJsonApiErrorNum::TOKEN_FORMAT_INVALID: + return "Invalid token format, must be alphanumeric_user:password"; + case RsJsonApiErrorNum::UNKNOWN_API_USER: + return "Unknown API user"; + case RsJsonApiErrorNum::WRONG_API_PASSWORD: + return "Wrong API password"; + case RsJsonApiErrorNum::API_USER_CONTAIN_COLON: + return "API user cannot contain colon character"; + case RsJsonApiErrorNum::AUTHORIZATION_REQUEST_DENIED: + return "User denied new token autorization"; + case RsJsonApiErrorNum::CANNOT_EXECUTE_BEFORE_RS_LOGIN: + return "This operation cannot be executed bedore RetroShare login"; + case RsJsonApiErrorNum::NOT_A_MACHINE_GUN: + return "Method must not be called in burst"; + default: + return "Error message for error: " + std::to_string(ev) + + " not available in category: " + name(); + } + } + + std::error_condition default_error_condition(int ev) const noexcept override; + + const static RsJsonApiErrorCategory instance; +}; + + +namespace std +{ +/** Register RsJsonApiErrorNum as an error condition enum, must be in std + * namespace */ +template<> struct is_error_condition_enum : true_type {}; +} + +/** Provide RsJsonApiErrorNum conversion to std::error_condition, must be in + * same namespace of RsJsonApiErrorNum */ +inline std::error_condition make_error_condition(RsJsonApiErrorNum e) noexcept +{ + return std::error_condition( + static_cast(e), RsJsonApiErrorCategory::instance ); +}; + + +class p3ConfigMgr; +class JsonApiResourceProvider; + class RsJsonApi { public: @@ -43,10 +111,12 @@ public: static const std::string DEFAULT_BINDING_ADDRESS; // 127.0.0.1 /** - * @brief Restart RsJsonApi server asynchronously. - * @jsonapi{development} + * @brief Restart RsJsonApi server. + * This method is asyncronous when called from JSON API. + * @jsonapi{development,manualwrapper} + * @return Success or error details */ - virtual void restart() = 0; + virtual std::error_condition restart() = 0; /** @brief Request RsJsonApi to stop and wait until it has stopped. * Do not expose this method to JSON API as fullstop must not be called from @@ -124,9 +194,9 @@ public: * @jsonapi{development,unauthenticated} * @param[in] user user name to authorize * @param[in] password password for the new user - * @return true if authorization succeded, false otherwise. + * @return if an error occurred details about it. */ - virtual bool requestNewTokenAutorization( + virtual std::error_condition requestNewTokenAutorization( const std::string& user, const std::string& password) = 0; /** Split a token in USER:PASSWORD format into user and password */ @@ -135,14 +205,13 @@ public: std::string& user, std::string& passwd ); /** - * Add new API auth (user,passwd) token to the authorized set. + * Add new API auth user, passwd to the authorized set. * @jsonapi{development} - * @param[in] user user name to autorize, must be alphanumerinc - * @param[in] password password for the user, must be alphanumerinc - * @return true if the token has been added to authorized, false if error - * occurred + * @param[in] user user name to autorize, must not contain ':' + * @param[in] password password for the user + * @return if some error occurred return details about it */ - virtual bool authorizeUser( + virtual std::error_condition authorizeUser( const std::string& user, const std::string& password ) = 0; /** @@ -164,9 +233,13 @@ public: * @brief Check if given JSON API auth token is authorized * @jsonapi{development,unauthenticated} * @param[in] token decoded - * @return tru if authorized, false otherwise + * @param[out] error optional storage for error details + * @return true if authorized, false otherwise */ - virtual bool isAuthTokenValid(const std::string& token) = 0; + virtual bool isAuthTokenValid( + const std::string& token, + std::error_condition& error = RS_DEFAULT_STORAGE_PARAM(std::error_condition) + ) = 0; /** * @brief Write version information to given paramethers @@ -182,3 +255,4 @@ public: virtual ~RsJsonApi() = default; }; + diff --git a/libretroshare/src/rsserver/rsinit.cc b/libretroshare/src/rsserver/rsinit.cc index db290ea63..2de431589 100644 --- a/libretroshare/src/rsserver/rsinit.cc +++ b/libretroshare/src/rsserver/rsinit.cc @@ -397,8 +397,8 @@ int RsInit::InitRetroShare(const RsConfigOptions& conf) #ifdef RS_JSONAPI // We create the JsonApiServer this early, because it is needed *before* login - RsInfo() << __PRETTY_FUNCTION__ - << "Allocating jsonAPI server (not launched yet)" << std::endl; + RsDbg() << __PRETTY_FUNCTION__ + << " Allocating JSON API server (not launched yet)" << std::endl; JsonApiServer* jas = new JsonApiServer(); jas->setListeningPort(conf.jsonApiPort); jas->setBindingAddress(conf.jsonApiBindAddress); diff --git a/libretroshare/src/serialiser/rstypeserializer.cc b/libretroshare/src/serialiser/rstypeserializer.cc index 47fd99e45..1b086576a 100644 --- a/libretroshare/src/serialiser/rstypeserializer.cc +++ b/libretroshare/src/serialiser/rstypeserializer.cc @@ -768,3 +768,37 @@ bool RsTypeSerializer::from_JSON( const std::string& memberName, } return ret; } + +void RsTypeSerializer::ErrConditionWrapper::serial_process( + RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx ) +{ + switch(j) + { + case RsGenericSerializer::SIZE_ESTIMATE: // fallthrough + case RsGenericSerializer::DESERIALIZE: // fallthrough + case RsGenericSerializer::SERIALIZE: // fallthrough + case RsGenericSerializer::FROM_JSON: + RsFatal() << __PRETTY_FUNCTION__ << " SerializeJob: " << j + << "is not supported on std::error_condition " << std::endl; + print_stacktrace(); + exit(-2); + case RsGenericSerializer::PRINT: // fallthrough + case RsGenericSerializer::TO_JSON: + { + constexpr RsGenericSerializer::SerializeJob rj = + RsGenericSerializer::TO_JSON; + + int32_t tNum = mec.value(); + RsTypeSerializer::serial_process(rj, ctx, tNum, "errorNumber"); + + std::string tStr = mec.category().name(); + RsTypeSerializer::serial_process(rj, ctx, tStr, "errorCategory"); + + tStr = mec.message(); + RsTypeSerializer::serial_process(rj, ctx, tStr, "errorMessage"); + break; + } + default: RsTypeSerializer::fatalUnknownSerialJob(j); + } +} diff --git a/libretroshare/src/serialiser/rstypeserializer.h b/libretroshare/src/serialiser/rstypeserializer.h index 550b89f07..55568632a 100644 --- a/libretroshare/src/serialiser/rstypeserializer.h +++ b/libretroshare/src/serialiser/rstypeserializer.h @@ -22,6 +22,12 @@ *******************************************************************************/ #pragma once +#include // for typeid +#include +#include +#include + + #include "serialiser/rsserial.h" #include "serialiser/rstlvbase.h" #include "serialiser/rstlvlist.h" @@ -34,11 +40,6 @@ #include "util/rsjson.h" #include "util/rsdebug.h" -#include // for typeid -#include -#include - - /* INTERNAL ONLY helper to avoid copy paste code for std::{vector,list,set} * Can't use a template function because T is needed for const_cast */ #define RsTypeSerializer_PRIVATE_TO_JSON_ARRAY() do \ @@ -132,7 +133,12 @@ struct RsTypeSerializer /// Generic types template - typename std::enable_if::value || !(std::is_base_of::value || std::is_enum::value || std::is_base_of::value )>::type + typename + std::enable_if< std::is_same::value || !( + std::is_base_of::value || + std::is_enum::value || + std::is_base_of::value || + std::is_same::value ) >::type static /*void*/ serial_process( RsGenericSerializer::SerializeJob j, RsGenericSerializer::SerializeContext& ctx, T& member, const std::string& member_name ) @@ -160,11 +166,7 @@ struct RsTypeSerializer ctx.mOk &= (ctx.mOk || ctx.mFlags & RsGenericSerializer::SERIALIZATION_FLAG_YIELDING) && from_JSON(member_name, member, ctx.mJson); break; - default: - std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: " - << static_cast::type>(j) - << std::endl; - exit(EINVAL); + default: fatalUnknownSerialJob(j); } } @@ -200,11 +202,7 @@ struct RsTypeSerializer (ctx.mOk || ctx.mFlags & RsGenericSerializer::SERIALIZATION_FLAG_YIELDING) && from_JSON(member_name, type_id, member, ctx.mJson); break; - default: - std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: " - << static_cast::type>(j) - << std::endl; - exit(EINVAL); + default: fatalUnknownSerialJob(j); } } @@ -362,11 +360,7 @@ struct RsTypeSerializer } break; } - default: - std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: " - << static_cast::type>(j) - << std::endl; - exit(EINVAL); + default: fatalUnknownSerialJob(j); } } @@ -441,12 +435,7 @@ struct RsTypeSerializer break; } - default: - std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: " - << static_cast::type>(j) - << std::endl; - exit(EINVAL); - break; + default: fatalUnknownSerialJob(j); } } @@ -505,7 +494,7 @@ struct RsTypeSerializer case RsGenericSerializer::FROM_JSON: RsTypeSerializer_PRIVATE_FROM_JSON_ARRAY(push_back); break; - default: break; + default: fatalUnknownSerialJob(j); } } @@ -564,7 +553,7 @@ struct RsTypeSerializer case RsGenericSerializer::FROM_JSON: RsTypeSerializer_PRIVATE_FROM_JSON_ARRAY(insert); break; - default: break; + default: fatalUnknownSerialJob(j); } } @@ -620,7 +609,7 @@ struct RsTypeSerializer case RsGenericSerializer::FROM_JSON: RsTypeSerializer_PRIVATE_FROM_JSON_ARRAY(push_back); break; - default: break; + default: fatalUnknownSerialJob(j); } } @@ -663,7 +652,7 @@ struct RsTypeSerializer && (v = t_RsFlags32(f), true); break; } - default: break; + default: fatalUnknownSerialJob(j); } } @@ -772,18 +761,15 @@ struct RsTypeSerializer break; } - default: - std::cerr << __PRETTY_FUNCTION__ << " Unknown serial job: " - << static_cast::type>(j) - << std::endl; - exit(EINVAL); - break; + default: fatalUnknownSerialJob(j); } } /// RsTlvItem derivatives only template - typename std::enable_if::value && !std::is_same::value>::type + typename std::enable_if< + std::is_base_of::value && !std::is_same::value + >::type static /*void*/ serial_process( RsGenericSerializer::SerializeJob j, RsGenericSerializer::SerializeContext& ctx, T& member, @@ -792,6 +778,21 @@ struct RsTypeSerializer serial_process(j, ctx, static_cast(member), memberName); } + /** std::error_condition + * supports only TO_JSON ErrConditionWrapper::serial_process will explode + * at runtime if a different SerializeJob is passed down */ + template + typename std::enable_if< std::is_base_of::value >::type + static /*void*/ serial_process( + RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx, + const T& cond, + const std::string& member_name ) + { + ErrConditionWrapper ew(cond); + serial_process(j, ctx, ew, member_name); + } + protected: //============================================================================// @@ -909,6 +910,27 @@ protected: t_RsTlvList& member, RsJson& jDoc ); + [[noreturn]] static void fatalUnknownSerialJob(int j) + { + RsFatal() << " Unknown serial job: " << j << std::endl; + print_stacktrace(); + exit(EINVAL); + } + + struct ErrConditionWrapper : RsSerializable + { + ErrConditionWrapper(const std::error_condition& ec): mec(ec) {} + + /** supports only TO_JSON if a different SerializeJob is passed it will + * explode at runtime */ + void serial_process( + RsGenericSerializer::SerializeJob j, + RsGenericSerializer::SerializeContext& ctx ) override; + + private: + const std::error_condition& mec; + }; + RS_SET_CONTEXT_DEBUG_LEVEL(1) }; diff --git a/libretroshare/src/util/rsdebug.cc b/libretroshare/src/util/rsdebug.cc index 4c126b656..93049e1b9 100644 --- a/libretroshare/src/util/rsdebug.cc +++ b/libretroshare/src/util/rsdebug.cc @@ -3,7 +3,9 @@ * * * libretroshare: retroshare core library * * * - * Copyright 2004-2008 by Robert Fernie * + * Copyright (C) 2004-2008 by Robert Fernie * + * Copyright (C) 2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * @@ -21,11 +23,25 @@ *******************************************************************************/ #include "util/rsdebug.h" -#include "util/rsthreads.h" -#include "util/rsdir.h" + +std::ostream &operator<<(std::ostream& out, const std::error_condition& err) +{ + return out << " error: " << err.value() << " " << err.message() + << " category: " << err.category().name(); +} + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +/// All the following lines are DEPRECATED!! #include -#include +#include + +#include "util/rsthreads.h" +#include "util/rsdir.h" #include "util/rstime.h" const int RS_DEBUG_STDERR = 1; /* stuff goes to stderr */ @@ -186,6 +202,3 @@ void rslog(const RsLog::logLvl lvl, RsLog::logInfo *info, const std::string &msg lineCount++; } } - - - diff --git a/libretroshare/src/util/rsdebug.h b/libretroshare/src/util/rsdebug.h index 227d5f8b7..1148f74f2 100644 --- a/libretroshare/src/util/rsdebug.h +++ b/libretroshare/src/util/rsdebug.h @@ -2,7 +2,8 @@ * RetroShare debugging utilities * * * * Copyright (C) 2004-2008 Robert Fernie * - * Copyright (C) 2019 Gioacchino Mazzurco * + * Copyright (C) 2019-2020 Gioacchino Mazzurco * + * Copyright (C) 2020 Asociación Civil Altermundi * * * * This program is free software: you can redistribute it and/or modify * * it under the terms of the GNU Lesser General Public License as * @@ -21,6 +22,10 @@ #pragma once #include +#include + +/** Stream helper for std::error_condition */ +std::ostream &operator<<(std::ostream& out, const std::error_condition& err); #ifdef __ANDROID__ # include @@ -41,12 +46,14 @@ struct t_RsLogger { inline t_RsLogger() = default; + typedef t_RsLogger stream_type; + template - inline t_RsLogger& operator<<(const T& val) + inline stream_type& operator<<(const T& val) { ostr << val; return *this; } /// needed for manipulators and things like std::endl - t_RsLogger& operator<<(std::ostream& (*pf)(std::ostream&)) + stream_type& operator<<(std::ostream& (*pf)(std::ostream&)) { if(pf == static_cast( &std::endl< char, std::char_traits > )) @@ -84,8 +91,10 @@ struct t_RsLogger { inline t_RsLogger() = default; + typedef decltype(std::cerr) stream_type; + template - inline std::ostream& operator<<(const T& val) + inline stream_type& operator<<(const T& val) { return std::cerr << static_cast(CATEGORY) << " " << time(nullptr) << " " << val; @@ -97,7 +106,7 @@ struct t_RsLogger /** * Comfortable debug message loggin, supports chaining like std::cerr but can * be easly and selectively disabled at compile time to reduce generated binary - * size and performance impact without too many #ifdef around. + * size and performance impact without too many \#ifdef around. * * To selectively debug your context you can just add something like this in * in that context, as an example for a class you can just add a line like this diff --git a/libretroshare/src/util/rsstring.cc b/libretroshare/src/util/rsstring.cc index 7b540156a..6dcb8bd21 100644 --- a/libretroshare/src/util/rsstring.cc +++ b/libretroshare/src/util/rsstring.cc @@ -205,6 +205,7 @@ bool ConvertUtf16ToUtf8(const std::wstring& source, std::string& dest) return true; } +#if 0 bool is_alphanumeric(char c) { return (c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z'); @@ -216,6 +217,7 @@ bool is_alphanumeric(const std::string& s) if(!is_alphanumeric(s[i])) return false; return true; } +#endif } } // librs::util diff --git a/libretroshare/src/util/rsstring.h b/libretroshare/src/util/rsstring.h index e2aceac68..b3fd2553c 100644 --- a/libretroshare/src/util/rsstring.h +++ b/libretroshare/src/util/rsstring.h @@ -30,10 +30,10 @@ namespace librs { bool ConvertUtf8ToUtf16(const std::string& source, std::wstring& dest); bool ConvertUtf16ToUtf8(const std::wstring& source, std::string& dest); - +#if 0 bool is_alphanumeric(char c) ; bool is_alphanumeric(const std::string& s); - +#endif } } // librs::util #ifdef WIN32 diff --git a/libretroshare/src/util/rsthreads.cc b/libretroshare/src/util/rsthreads.cc index c2b84ff70..e4c4866a3 100644 --- a/libretroshare/src/util/rsthreads.cc +++ b/libretroshare/src/util/rsthreads.cc @@ -4,7 +4,7 @@ * libretroshare: retroshare core library * * * * Copyright (C) 2004-2007 Robert Fernie * - * Copyright (C) 2016-2019 Gioacchino Mazzurco * + * Copyright (C) 2016-2020 Gioacchino Mazzurco * * Copyright (C) 2019-2020 Asociación Civil Altermundi * * * * This program is free software: you can redistribute it and/or modify * @@ -23,7 +23,7 @@ *******************************************************************************/ #include -#include +#include #include #include @@ -95,6 +95,9 @@ void RsThread::resetTid() } RsThread::RsThread() : mHasStopped(true), mShouldStop(false), mLastTid() +#ifdef RS_THREAD_FORCE_STOP + , mStopTimeout(0) +#endif { resetTid(); } bool RsThread::isRunning() { return !mHasStopped; } @@ -117,6 +120,10 @@ void RsThread::wrapRun() void RsThread::fullstop() { +#ifdef RS_THREAD_FORCE_STOP + const rstime_t stopRequTS = time(nullptr); +#endif + askForStop(); const pthread_t callerTid = pthread_self(); @@ -141,6 +148,32 @@ void RsThread::fullstop() RsDbg() << __PRETTY_FUNCTION__ << " " << i*0.2 << " seconds passed" << " waiting for thread: " << std::hex << mLastTid << std::dec << " " << mFullName << " to stop" << std::endl; + +#ifdef RS_THREAD_FORCE_STOP + if(mStopTimeout && time(nullptr) > stopRequTS + mStopTimeout) + { + RsErr() << __PRETTY_FUNCTION__ << " thread mLastTid: " << std::hex + << mLastTid << " mTid: " << mTid << std::dec << " " + << mFullName + << " ignored our nice stop request for more then " + << mStopTimeout + << " seconds, will be forcefully stopped. " + << "Please submit a report to RetroShare developers" + << std::endl; + + const auto terr = pthread_cancel(mTid); + if(terr == 0) mHasStopped = true; + else + { + RsErr() << __PRETTY_FUNCTION__ << " pthread_cancel(" + << std::hex << mTid << std::dec <<") returned " + << terr << " " << rsErrnoName(terr) << std::endl; + print_stacktrace(); + } + + break; + } +#endif // def RS_THREAD_FORCE_STOP } } diff --git a/libretroshare/src/util/rsthreads.h b/libretroshare/src/util/rsthreads.h index 5692bb256..3ff803f99 100644 --- a/libretroshare/src/util/rsthreads.h +++ b/libretroshare/src/util/rsthreads.h @@ -35,6 +35,9 @@ #include "util/rsmemory.h" #include "util/rsdeprecate.h" +#ifdef RS_THREAD_FORCE_STOP +# include "util/rstime.h" +#endif //#define RSMUTEX_DEBUG @@ -249,6 +252,13 @@ protected: * of this method, @see JsonApiServer for an usage example. */ virtual void onStopRequested() {} +#ifdef RS_THREAD_FORCE_STOP + /** Set last resort timeout to forcefully kill thread if it didn't stop + * nicely, one should never use this, still we needed to introduce this + * to investigate some bugs in external libraries */ + void setStopTimeout(rstime_t timeout) { mStopTimeout = timeout; } +#endif + private: /** Call @see run() setting the appropriate flags around it*/ void wrapRun(); @@ -277,6 +287,11 @@ private: * and that might happens concurrently (or just before) a debug message * being printed, thus causing the debug message to print a mangled value.*/ pthread_t mLastTid; + +#ifdef RS_THREAD_FORCE_STOP + /// @see setStopTimeout + rstime_t mStopTimeout; +#endif }; /** diff --git a/retroshare-gui/src/gui/settings/JsonApiPage.cc b/retroshare-gui/src/gui/settings/JsonApiPage.cc index 6c6dee018..5f1bb225f 100644 --- a/retroshare-gui/src/gui/settings/JsonApiPage.cc +++ b/retroshare-gui/src/gui/settings/JsonApiPage.cc @@ -112,28 +112,41 @@ QString JsonApiPage::helpText() const { return ""; } bool JsonApiPage::checkStartJsonApi() { - if(!Settings->getJsonApiEnabled()) - return false; + if(!Settings->getJsonApiEnabled()) return false; rsJsonApi->setListeningPort(Settings->getJsonApiPort()); rsJsonApi->setBindingAddress(Settings->getJsonApiListenAddress().toStdString()); - rsJsonApi->restart(); + + const auto rErr = rsJsonApi->restart(); + if(rErr == RsJsonApiErrorNum::NOT_A_MACHINE_GUN) + { + RsDbg() << __PRETTY_FUNCTION__ << " apparently the user is attempting " + << "to restart JSON API service in a burst. Re-scheduling " + << "restart in a while..." << std::endl; + RsThread::async([]() + { + std::this_thread::sleep_for(std::chrono::seconds(10)); + rsJsonApi->restart(); + }); + } + else if(rErr) + { + RsErr() << __PRETTY_FUNCTION__ << rErr << std::endl; + return false; + } return true; } /*static*/ void JsonApiPage::checkShutdownJsonApi() { - if(!rsJsonApi->isRunning()) return; rsJsonApi->fullstop(); // this is a blocks until the thread is terminated. } void JsonApiPage::onApplyClicked() { - // restart - - checkShutdownJsonApi(); - checkStartJsonApi(); + // restart + checkStartJsonApi(); } void JsonApiPage::checkToken(QString s)