merge intermediate state of JSON-156, so the bugfix for context-saved function parameters can already be released.

JSON-170
Roker 3 years ago
commit 92834930cd

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

@ -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<void(PEP_SESSION)>& 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();
}

@ -1,31 +1,37 @@
#ifndef JSON_ADAPTER_CONTEXT_HH
#define JSON_ADAPTER_CONTEXT_HH
#include <string>
#include "json_spirit/json_spirit_value.h"
#include <map>
#include <pEp/pEpEngine.h>
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<void(PEP_SESSION)>& fn) = 0;
// delegate call to the 'ja' member
void cache(const std::string& func_name, const std::function<void(PEP_SESSION)>& 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<int, size_t> obj_store;
JsonAdapterBase* ja;
};
#endif // JSON_ADAPTER_CONTEXT_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;
}

@ -26,6 +26,7 @@
#include <pEp/keymanagement.h>
#include <pEp/call_with_lock.hh>
#include <pEp/constant_time_algo.hh>
#include <pEp/status_to_string.hh> // from libpEpAdapter.
#include <pEp/locked_queue.hh>
@ -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;
}

@ -5,15 +5,27 @@
#include <pEp/message.h>
#include <pEp/sync_api.h>
#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<void(PEP_SESSION)>& 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<void(PEP_SESSION)>& 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:

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

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

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

@ -1,5 +1,6 @@
#include <gtest/gtest.h>
#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<void(PEP_SESSION)>& 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 );

Loading…
Cancel
Save