Merge pull request #1774 from G10h4ck/json_api_improvements

JSON API improvements
This commit is contained in:
csoler 2020-02-07 23:16:04 +01:00 committed by GitHub
commit a9f24c85a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 530 additions and 153 deletions

@ -1 +1 @@
Subproject commit c064abd74b27e1cc440917e9dbac800316bb8470
Subproject commit 879d5dfe8dcd8995be753120cf1b8bab4dd2ec82

View File

@ -16,7 +16,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>
*
* SPDX-FileCopyrightText: 2004-2019 RetroShare Team <contact@retroshare.cc>
* SPDX-FileCopyrightText: 2004-2020 RetroShare Team <contact@retroshare.cc>
* 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<restbed::Service>()),
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<rb::Session> 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<rb::Session> session)
{
auto reqSize = session->get_request()->get_header("Content-Length", 0);
session->fetch( static_cast<size_t>(reqSize), [this](
const std::shared_ptr<rb::Session> 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<rb::Session> session,
const std::function<void (const std::shared_ptr<rb::Session>)>& 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<bool (const std::string&, const std::string&)>& 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<uint32_t>(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<uint32_t>(noOptimiz) + 1 == 0 ?
success() : failure(RsJsonApiErrorNum::WRONG_API_PASSWORD);
}
std::map<std::string, std::string> 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<std::shared_ptr<rb::Resource> > 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<rb::Service>(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<restbed::Service>();
}
auto tService = std::make_shared<restbed::Service>();
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<rb::Service>(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<RsJsonApiErrorNum>(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);
}
}

View File

@ -30,6 +30,7 @@
#include <set>
#include <functional>
#include <vector>
#include <atomic>
#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<std::string,std::string> 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<const JsonApiResourceProvider>,
std::less<const JsonApiResourceProvider> > 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<std::shared_ptr<restbed::Service>> which
* will automatically handle atomic access properly all the times
*/
std::shared_ptr<restbed::Service> 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<rstime_t> 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();
};

View File

@ -1,8 +1,9 @@
/*
* RetroShare JSON API public header
*
* Copyright (C) 2018-2019 Gioacchino Mazzurco <gio.eigenlab.org>
* Copyright (C) 2018-2020 Gioacchino Mazzurco <gio.eigenlab.org>
* Copyright (C) 2019 Cyril Soler <csoler@users.sourceforge.net>
* Copyright (C) 2020 Asociación Civil Altermundi <info@altermundi.net>
*
* 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 <map>
#include <string>
#include <cstdint>
#include <system_error>
#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<RsJsonApiErrorNum>(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<RsJsonApiErrorNum> : 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<int>(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;
};

View File

@ -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);

View File

@ -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);
}
}

View File

@ -22,6 +22,12 @@
*******************************************************************************/
#pragma once
#include <typeinfo> // for typeid
#include <type_traits>
#include <errno.h>
#include <system_error>
#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 <typeinfo> // for typeid
#include <type_traits>
#include <errno.h>
/* INTERNAL ONLY helper to avoid copy paste code for std::{vector,list,set}<T>
* 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 T>
typename std::enable_if<std::is_same<RsTlvItem,T>::value || !(std::is_base_of<RsSerializable,T>::value || std::is_enum<T>::value || std::is_base_of<RsTlvItem,T>::value )>::type
typename
std::enable_if< std::is_same<RsTlvItem,T>::value || !(
std::is_base_of<RsSerializable,T>::value ||
std::is_enum<T>::value ||
std::is_base_of<RsTlvItem,T>::value ||
std::is_same<std::error_condition,T>::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<std::underlying_type<decltype(j)>::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<std::underlying_type<decltype(j)>::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<std::underlying_type<decltype(j)>::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<std::underlying_type<decltype(j)>::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<N>(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<std::underlying_type<decltype(j)>::type>(j)
<< std::endl;
exit(EINVAL);
break;
default: fatalUnknownSerialJob(j);
}
}
/// RsTlvItem derivatives only
template<typename T>
typename std::enable_if<std::is_base_of<RsTlvItem,T>::value && !std::is_same<RsTlvItem,T>::value>::type
typename std::enable_if<
std::is_base_of<RsTlvItem,T>::value && !std::is_same<RsTlvItem,T>::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<RsTlvItem&>(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 T>
typename std::enable_if< std::is_base_of<std::error_condition,T>::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<TLV_CLASS,TLV_TYPE>& 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)
};

View File

@ -3,7 +3,9 @@
* *
* libretroshare: retroshare core library *
* *
* Copyright 2004-2008 by Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2004-2008 by Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2020 Asociación Civil Altermundi <info@altermundi.net> *
* *
* 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 <map>
#include <stdio.h>
#include <cstdio>
#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++;
}
}

View File

@ -2,7 +2,8 @@
* RetroShare debugging utilities *
* *
* Copyright (C) 2004-2008 Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2019 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2019-2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2020 Asociación Civil Altermundi <info@altermundi.net> *
* *
* 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 <ostream>
#include <system_error>
/** Stream helper for std::error_condition */
std::ostream &operator<<(std::ostream& out, const std::error_condition& err);
#ifdef __ANDROID__
# include <android/log.h>
@ -41,12 +46,14 @@ struct t_RsLogger
{
inline t_RsLogger() = default;
typedef t_RsLogger stream_type;
template<typename T>
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::ostream& (*)(std::ostream&)>(
&std::endl< char, std::char_traits<char> > ))
@ -84,8 +91,10 @@ struct t_RsLogger
{
inline t_RsLogger() = default;
typedef decltype(std::cerr) stream_type;
template<typename T>
inline std::ostream& operator<<(const T& val)
inline stream_type& operator<<(const T& val)
{
return std::cerr << static_cast<char>(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

View File

@ -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

View File

@ -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

View File

@ -4,7 +4,7 @@
* libretroshare: retroshare core library *
* *
* Copyright (C) 2004-2007 Robert Fernie <retroshare@lunamutt.com> *
* Copyright (C) 2016-2019 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2016-2020 Gioacchino Mazzurco <gio@eigenlab.org> *
* Copyright (C) 2019-2020 Asociación Civil Altermundi <info@altermundi.net> *
* *
* This program is free software: you can redistribute it and/or modify *
@ -23,7 +23,7 @@
*******************************************************************************/
#include <iostream>
#include <time.h>
#include <ctime>
#include <thread>
#include <chrono>
@ -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
}
}

View File

@ -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
};
/**

View File

@ -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)