diff --git a/README.md b/README.md index 16d523e..17be296 100644 --- a/README.md +++ b/README.md @@ -189,22 +189,44 @@ the adapter and its functions. The JSON Server Adapter can be started on demand. It checks automatically whether an instance for the same user on the machine -is already running and if yes it ends itself gracefully. +is already running and if yes it ends itself gracefully. (TODO!) If there is no running server found the newly started server creates the server token file and forks itself into background (if not prevented via "-d" commandline switch). +### Multi-Client handling -### Session handling +The p≡p JSON server adapter supports multiple clients, communicating with the +server at the same time. Each client instance is identified by a client ID, +that the clients put into each JSON RPC request in the field "clientid". -When using the p≡p engine, a session is needed to which any adapter can -connect. The p≡p JSON Server Adapter automatically creates one session per +The client ID is a UUID Version 4, created by the client at startup and has to +be stable while the client application runs. When the client restarts, a new +client ID should be created to avoid interferene with data from the old client +session. + +The p≡p JSON server adapter stores data (e.g., a so called "config cache", see +next section) associated with each client ID. After a timeout period with no +JSON RPC calls and no open client connections these data are removed +automatically. Run the mini adapter with -h to see the compiled-in default +timeout value. + +### PEP_SESSION handling + +When using the p≡p engine, a `PEP_SESSION` is needed as parameter to many API +functions. The p≡p JSON Server Adapter automatically creates one session per HTTP client connection (and also closes that session automatically when the client connections is closed). Therefore, the client does not need to take -care of the session management. However, the client has to set up a [HTTP +care of the session management. However, the client should set up a [HTTP persistent -connection](https://en.wikipedia.org/wiki/HTTP_persistent_connection). +connection](https://en.wikipedia.org/wiki/HTTP_persistent_connection) to +minify session creation and destruction. + +There is a configuration cache, that stores all `config_*()` calls and its +configured values. Whenever a new PEP_SESSION is needed for this client +(identified via its client ID, see previous section), all config values +are applied to this new session, too, before the session is used. ### API Principles @@ -352,13 +374,16 @@ Result: An complete overview with all functions that are callable from the client can be found in the [API Reference](pEp JSON Server Adapter/API Reference). -That API reference is a generated file that shows the current API briefly. +That API reference is a generated file (at irregular intervals) that shows the current API briefly. There is also a (currently manually written) file that holts a copy of the documentation from the Engine's header files: [API reference detail.md] +BEWARE: Because this file is not auto-generated, yet, it might be even more outdated! + Most of the callable functions are functions from the C API of the p≡p Engine. They are described in detail, incl. pre- and post-conditions in -the appropriate C header files of the Engine. +the appropriate C header files of the Engine, which are the authoritative source +of documentation in cases of doubt. ### Authentication @@ -385,7 +410,7 @@ file that has user-only read permissions. token". -### Callbacks / Reverse connection +### Callbacks / Event delivery p≡p applications must register callback handlers at the Engine. At the moment there are these callbacks: @@ -396,21 +421,13 @@ there are these callbacks: The JSON adapter register its own functions at the Engine which propagate these events to all connected clients. -The event propagation to the clients are done via the special API function -`pollForEvents()`. This function expects a special "session" string (which can be -optained by a call to `create_session()`) and a numerical -timeout (in seconds) and blocks until at least one event arrives, which is returned. -If no event arrives until the given timeout, this function returns an empty JSON array. -If events arrive between two calls of `pollForEvents()` (with the same "session") they -are cached by the JSON adapter and returned in the next call, which returns immediately. - -A call to `close_session()` with delete all pending events for that session. +The event propagation to the clients are done via long polling: Clients +call the function `pollForEvents()` that blocks until an event +from the Engine arrives. TODO: remove create_session(), use client ID instead? -TODO: Use WebSockets: In fact this is also a type of "long polling" and an open -TCP connection, opened by the Client. But it requires additional code in the JSON -Adapter for the WebSockets protocol and a mechanism how to transfer the underlaying -TCP socket and buffer from the libevent library to the WebSockets implementation. +It is planned to switch to use WebSockets: In fact this is also a type of +"long polling" and an open TCP connection, opened by the Client. See: https://pep.foundation/jira/browse/JSON-128 diff --git a/server/context.cc b/server/context.cc index 1e43d76..41514f0 100644 --- a/server/context.cc +++ b/server/context.cc @@ -1,5 +1,7 @@ #include "context.hh" #include "logger.hh" +#include "json-adapter.hh" + namespace { @@ -11,6 +13,19 @@ namespace } +bool Context::verify_security_token(const std::string& token) const +{ + return ja->verify_security_token(token); +} + + +// Cache a certain function call. See JSON-155. +void Context::cache(const std::string& func_name, const std::function& fn) +{ + ja->cache(func_name, fn); +} + + void Context::store(int position, size_t value) { DEBUG_OUT( Log(), "Store value %zu for position %d.", value, position); @@ -29,9 +44,3 @@ size_t Context::retrieve(int position) throw; } } - - -void Context::clear() -{ - obj_store.clear(); -} diff --git a/server/context.hh b/server/context.hh index ebb6953..a9bd3c6 100644 --- a/server/context.hh +++ b/server/context.hh @@ -1,31 +1,37 @@ #ifndef JSON_ADAPTER_CONTEXT_HH #define JSON_ADAPTER_CONTEXT_HH -#include -#include "json_spirit/json_spirit_value.h" #include #include + +class JsonAdapterBase; + class Context { public: - virtual ~Context() = default; + Context(JsonAdapterBase* _ja) : ja{_ja} {} + + Context(const Context&) = delete; + void operator=(const Context&) = delete; - virtual bool verify_security_token(const std::string& token) const = 0; - virtual void augment(json_spirit::Object& returnObject) = 0; + // delegate call to the 'ja' member + bool verify_security_token(const std::string& token) const ; // Cache a certain function call. See JSON-155. - virtual void cache(const std::string& func_name, const std::function& fn) = 0; + // delegate call to the 'ja' member + void cache(const std::string& func_name, const std::function& fn); // store and retrieve other parameters into the context. // that allows semantic actions based on other function parameters // KISS: at the moment only "size_t" objects are supported. - virtual void store(int position, size_t value); - virtual size_t retrieve(int position); - virtual void clear(); + void store(int position, size_t value); + size_t retrieve(int position); + void clear(); private: std::map obj_store; + JsonAdapterBase* ja; }; #endif // JSON_ADAPTER_CONTEXT_HH diff --git a/server/function_map.hh b/server/function_map.hh index c32366f..a30f82f 100644 --- a/server/function_map.hh +++ b/server/function_map.hh @@ -209,9 +209,6 @@ public: rs.emplace_back("outParams", std::move(out_params)); rs.emplace_back("return", std::move(ret)); - context->augment(rs); // used e.g. add some debug infos to the status return value - context->clear(); // clear all stored values, if any. - return rs; } diff --git a/server/json-adapter.cc b/server/json-adapter.cc index 3f7b825..75c3dd2 100644 --- a/server/json-adapter.cc +++ b/server/json-adapter.cc @@ -26,6 +26,7 @@ #include #include +#include #include // from libpEpAdapter. #include @@ -333,18 +334,12 @@ void JsonAdapter::shutdown(timeval* t) bool JsonAdapter::verify_security_token(const std::string& s) const { check_guard(); - if(s!=i->token) + const bool eq = pEp::constant_time_equal(s, i->token); + if( eq==false ) { Log(Logger::Notice) << "sec_token=\"" << i->token << "\" (len=" << i->token.size() << ") is unequal to \"" << s << "\" (len=" << s.size() << ")!"; } - return s == i->token; -} - - -void JsonAdapter::augment(json_spirit::Object& returnObject) -{ - check_guard(); - // nothing to do anymore. + return eq; } diff --git a/server/json-adapter.hh b/server/json-adapter.hh index 32a8b1b..acfe3a3 100644 --- a/server/json-adapter.hh +++ b/server/json-adapter.hh @@ -5,15 +5,27 @@ #include #include #include "registry.hh" -#include "context.hh" #include "logger.hh" #include "server_version.hh" +#include "json_spirit/json_spirit_value.h" class SessionRegistry; -class JsonAdapter : public Context +// allow mocking the JsonAdapter in unittest_rpc +class JsonAdapterBase +{ +public: + ~JsonAdapterBase() = default; + virtual bool verify_security_token(const std::string& s) const = 0; + + // Cache a certain function call. See JSON-155. + virtual void cache(const std::string& fn_name, const std::function& func) = 0; +}; + + +class JsonAdapter : public JsonAdapterBase { public: @@ -75,8 +87,6 @@ public: // returns 'true' if 's' is the security token created by the function above. virtual bool verify_security_token(const std::string& s) const override; - virtual void augment(json_spirit::Object& returnObject) override; - virtual void cache(const std::string& fn_name, const std::function& func) override; // returns the version of the JsonAdapter @@ -96,8 +106,8 @@ public: static JsonAdapter& getInstance(); - static SessionRegistry& getSessionRegistry(); - + static SessionRegistry& getSessionRegistry(); + JsonAdapter& startup(::messageToSend_t messageToSend); protected: diff --git a/server/json_rpc.cc b/server/json_rpc.cc index 529fa18..4eeaecc 100644 --- a/server/json_rpc.cc +++ b/server/json_rpc.cc @@ -1,3 +1,4 @@ +#include "context.hh" #include "json_rpc.hh" #include "json_spirit/json_spirit_utils.h" #include "json_spirit/json_spirit_writer.h" @@ -72,7 +73,7 @@ js::Object make_request(const std::string& functionName, const js::Array& parame using json_spirit::find_value; -js::Object call(const FunctionMap& fm, const js::Object& request, Context* context) +js::Object call(const FunctionMap& fm, const js::Object& request, JsonAdapterBase* ja) { Logger L("jrpc:call"); int request_id = -1; @@ -86,11 +87,14 @@ js::Object call(const FunctionMap& fm, const js::Object& request, Context* conte const auto sec_token = find_value(request, "security_token"); const std::string sec_token_s = (sec_token.type()==js::str_type ? sec_token.get_str() : std::string() ); // missing or non-string "security_token" --> empty string. - if( context->verify_security_token(sec_token_s)==false ) + if( ja->verify_security_token(sec_token_s)==false ) { return make_error(JSON_RPC::INVALID_REQUEST, "Invalid request: Wrong security token.", request, request_id); } + const auto client_id = find_value(request, "client_id"); + const std::string client_id_s = (client_id.type()==js::str_type ? client_id.get_str() : std::string() ); // missing or non-string "client_id" --> empty string. + const auto method = find_value(request, "method"); if(method.type()!=js::str_type) { @@ -127,7 +131,8 @@ js::Object call(const FunctionMap& fm, const js::Object& request, Context* conte DEBUG_OUT(L, "method_name=\"" + method_name + "\"\n" "params=" + js::write(params) ); - const js::Value result = fn->second->call(p, context); + Context context{ja}; + const js::Value result = fn->second->call(p, &context); DEBUG_OUT(L, "result=" + js::write(result, js::raw_utf8) ); return make_result(result, request_id); diff --git a/server/json_rpc.hh b/server/json_rpc.hh index 0fdf8a7..9d5cb7b 100644 --- a/server/json_rpc.hh +++ b/server/json_rpc.hh @@ -2,7 +2,6 @@ #define JSON_RPC_HH #include "json_spirit/json_spirit_value.h" -#include "context.hh" #include "function_map.hh" namespace js = json_spirit; @@ -17,11 +16,13 @@ enum class JSON_RPC INTERNAL_ERROR = -32603, }; +class JsonAdapterBase; + // Server side: // parse the JSON-RPC 2.0 compatible "request", call the C function // and create an appropiate "response" object (containing a result or an error) -js::Object call(const FunctionMap& fm, const js::Object& request, Context* context); +js::Object call(const FunctionMap& fm, const js::Object& request, JsonAdapterBase* ja); // create a JSON-RPC 2.0 compatible result response object //js::Object make_result(const js::Value& result, int id); diff --git a/server/server_version.cc b/server/server_version.cc index ca0af92..d215d3c 100644 --- a/server/server_version.cc +++ b/server/server_version.cc @@ -75,7 +75,8 @@ static const std::string VersionName = // 41a,b were skipped, intentionally // "(42) Gotha"; // JSON-152: 2-parameter version of pollForEvents(). // "(43) Wandersleben"; // JSON-153 passphrase support. *sigh* - "(44) Neudietendorf"; // replace my own sync thread code by libpEpAdapter's implementation. +// "(44) Neudietendorf"; // replace my own sync thread code by libpEpAdapter's implementation. + "(45) Kreuz Erfurt"; // fix of context-saved function parameters that would cause trouble when >1 request is processed in parallel. } // end of anonymous namespace //////////////////////////////////////////////////////////////////////////// diff --git a/server/unittest_rpc.cc b/server/unittest_rpc.cc index 013e8ac..9df517e 100644 --- a/server/unittest_rpc.cc +++ b/server/unittest_rpc.cc @@ -1,5 +1,6 @@ #include #include "json_rpc.hh" +#include "json-adapter.hh" #include "function_map.hh" #include "c_string.hh" #include "json_spirit/json_spirit_reader.h" @@ -24,11 +25,10 @@ std::ostream& operator<<(std::ostream& os, const Value& value) namespace { -class DummyContext : public Context +class DummyAdapter : public JsonAdapterBase { public: virtual bool verify_security_token(const std::string& token) const override { return true; } - virtual void augment(js::Object&) override { /* do nothing */ } virtual void cache(const std::string& func_name, const std::function& fn) override { @@ -37,7 +37,7 @@ public: }; -DummyContext dummyContext; +DummyAdapter dummyAdapter; // some example & test functions: @@ -163,7 +163,7 @@ TEST_P( RpcTest, Meh ) js::read_or_throw(v.result, expected_result); auto r = request; - const js::Value actual_result = call( test_functions, request.get_obj(), &dummyContext); + const js::Value actual_result = call( test_functions, request.get_obj(), &dummyAdapter); js::Object result_obj = actual_result.get_obj(); js::Object::iterator q = std::find_if(result_obj.begin(), result_obj.end(), [](const js::Pair& v){ return js::Config::get_name(v) == "thread_id"; } ); result_obj.erase( q );