From 11d36a9f7e16aa9992835f8ce06f0e1e5297b131 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Bollo?= Date: Mon, 4 Apr 2016 17:48:50 +0200 Subject: [PATCH] work in progress, session handling MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Change-Id: I0393346280825e24a34e8db386160d857723b841 Signed-off-by: José Bollo --- include/afb-plugin.h | 17 +- include/afb-req-itf.h | 26 ++- plugins/CMakeLists.txt | 2 +- plugins/afm-main-plugin/afm-main-plugin.c | 283 ++++++++++++++++++------------ plugins/samples/ClientCtx.c | 9 +- plugins/samples/HelloWorld.c | 3 +- plugins/session/token-api.c | 4 +- src/afb-apis.c | 112 +++++------- src/afb-apis.h | 9 +- src/afb-hreq.c | 115 ++++++++++-- src/afb-hreq.h | 6 +- src/http-svc.c | 34 +--- src/local-def.h | 55 +----- src/main.c | 5 +- src/session.c | 132 +++++++------- src/session.h | 22 +-- 16 files changed, 458 insertions(+), 376 deletions(-) diff --git a/include/afb-plugin.h b/include/afb-plugin.h index 8ca72704..f2f4d6ec 100644 --- a/include/afb-plugin.h +++ b/include/afb-plugin.h @@ -54,12 +54,27 @@ struct AFB_plugin void (*freeCtxCB)(void*); // callback to free application context [null for standard free] }; +/* config mode */ +enum AFB_Mode { + AFB_MODE_LOCAL = 0, + AFB_MODE_REMOTE, + AFB_MODE_GLOBAL +}; + +/* typedef enum AFB_pluginE AFB_pluginE; typedef enum AFB_sessionE AFB_sessionE; typedef void (*AFB_apiCB)(struct afb_req); typedef void (*AFB_freeCtxCB)(void*); typedef struct AFB_restapi AFB_restapi; typedef struct AFB_plugin AFB_plugin; +*/ + +struct AFB_interface +{ + int verbose; + enum AFB_Mode mode; +}; -extern const struct AFB_plugin *pluginRegister (); +extern const struct AFB_plugin *pluginRegister (const struct AFB_interface *interface); diff --git a/include/afb-req-itf.h b/include/afb-req-itf.h index c593440b..a9b768db 100644 --- a/include/afb-req-itf.h +++ b/include/afb-req-itf.h @@ -29,12 +29,15 @@ struct afb_req_itf { void (*iterate)(void *data, int (*iterator)(void *closure, struct afb_arg arg), void *closure); void (*fail)(void *data, const char *status, const char *info); void (*success)(void *data, struct json_object *obj, const char *info); + int (*session_create)(void *data); + int (*session_check)(void *data, int refresh); + void (*session_close)(void *data); }; struct afb_req { const struct afb_req_itf *itf; void *data; - void **context; + void *context; }; static inline struct afb_arg afb_req_get(struct afb_req req, const char *name) @@ -67,6 +70,27 @@ static inline void afb_req_success(struct afb_req req, struct json_object *obj, req.itf->success(req.data, obj, info); } +static inline int afb_req_session_create(struct afb_req req) +{ + int result = req.itf->session_create(req.data); + if (!result) + afb_req_fail(req, "fail", "Can't create the session"); + return result; +} + +static inline int afb_req_session_check(struct afb_req req, int refresh) +{ + int result = req.itf->session_check(req.data, refresh); + if (!result) + afb_req_fail(req, "fail", "Token chek failed for the session"); + return result; +} + +static inline void afb_req_session_close(struct afb_req req) +{ + req.itf->session_close(req.data); +} + #if !defined(_GNU_SOURCE) # error "_GNU_SOURCE must be defined for using vasprintf" #endif diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 5d8b26ac..c60e3527 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,4 +1,4 @@ -#ADD_SUBDIRECTORY(afm-main-plugin) +ADD_SUBDIRECTORY(afm-main-plugin) ADD_SUBDIRECTORY(session) ADD_SUBDIRECTORY(samples) #ADD_SUBDIRECTORY(audio) diff --git a/plugins/afm-main-plugin/afm-main-plugin.c b/plugins/afm-main-plugin/afm-main-plugin.c index 0b6b8680..fbf5b590 100644 --- a/plugins/afm-main-plugin/afm-main-plugin.c +++ b/plugins/afm-main-plugin/afm-main-plugin.c @@ -18,201 +18,268 @@ #define _GNU_SOURCE /* See feature_test_macros(7) */ #include +#include +#include -#include "local-def.h" +#include "afb-plugin.h" +#include "afb-req-itf.h" #include "utils-jbus.h" -static const char _id_[] = "id"; -static const char _runid_[] = "runid"; -static char _runnables_[] = "runnables"; -static char _detail_[] = "detail"; -static char _start_[] = "start"; -static char _terminate_[] = "terminate"; -static char _stop_[] = "stop"; -static char _continue_[] = "continue"; -static char _runners_[] = "runners"; -static char _state_[] = "state"; -static char _install_[] = "install"; -static char _uninstall_[] = "uninstall"; -static const char _mode_[] = "mode"; -static const char _local_[] = "local"; -static const char _remote_[]= "remote"; -static const char _auto_[] = "auto"; -static const char _uri_[] = "uri"; +static const char _auto_[] = "auto"; +static const char _continue_[] = "continue"; +static const char _detail_[] = "detail"; +static const char _id_[] = "id"; +static const char _install_[] = "install"; +static const char _local_[] = "local"; +static const char _mode_[] = "mode"; +static const char _remote_[] = "remote"; +static const char _runid_[] = "runid"; +static const char _runnables_[] = "runnables"; +static const char _runners_[] = "runners"; +static const char _start_[] = "start"; +static const char _state_[] = "state"; +static const char _stop_[] = "stop"; +static const char _terminate_[] = "terminate"; +static const char _uninstall_[] = "uninstall"; +static const char _uri_[] = "uri"; + +static const struct AFB_interface *interface; static struct jbus *jbus; -static struct json_object *embed(AFB_request *request, const char *tag, struct json_object *obj) +static struct json_object *embed(const char *tag, struct json_object *obj) { struct json_object *result; if (obj == NULL) result = NULL; - else if (!tag) { - request->errcode = MHD_HTTP_OK; + else if (!tag) result = obj; - } else { result = json_object_new_object(); if (result == NULL) { /* can't embed */ result = obj; - request->errcode = MHD_HTTP_INTERNAL_SERVER_ERROR; } else { /* TODO why is json-c not returning a status? */ json_object_object_add(result, tag, obj); - request->errcode = MHD_HTTP_OK; } } return result; } -static struct json_object *call_void(AFB_request *request, AFB_PostItem *item) +static void embed_call_void(struct afb_req request, const char *method) { - struct json_object *obj = jbus_call_sj_sync(jbus, request->method, "true"); - if (verbose) - fprintf(stderr, "(afm-main-plugin) call_void: true -> %s\n", obj ? json_object_to_json_string(obj) : "NULL"); - request->errcode = obj ? MHD_HTTP_OK : MHD_HTTP_FAILED_DEPENDENCY; - return obj; + struct json_object *obj = jbus_call_sj_sync(jbus, method, "true"); + if (interface->verbose) + fprintf(stderr, "(afm-main-plugin) %s(true) -> %s\n", method, obj ? json_object_to_json_string(obj) : "NULL"); + if (obj == NULL) { + afb_req_fail(request, "failed", "framework daemon failure"); + return; + } + obj = embed(method, obj); + if (obj == NULL) { + afb_req_fail(request, "failed", "framework daemon failure"); + return; + } + afb_req_success(request, obj, NULL); } -static struct json_object *call_appid(AFB_request *request, AFB_PostItem *item) +static void call_appid(struct afb_req request, const char *method) { struct json_object *obj; char *sid; - const char *id = getQueryValue(request, _id_); + const char *id = afb_req_argument(request, _id_); if (id == NULL) { - request->errcode = MHD_HTTP_BAD_REQUEST; - return NULL; + afb_req_fail(request, "bad-request", "missing 'id'"); + return; } - if (0 >= asprintf(&sid, "\"%s\"", id)) { - request->errcode = MHD_HTTP_INTERNAL_SERVER_ERROR; - return NULL; + if (asprintf(&sid, "\"%s\"", id) <= 0) { + afb_req_fail(request, "server-error", "out of memory"); + return; } - obj = jbus_call_sj_sync(jbus, request->method, sid); - if (verbose) - fprintf(stderr, "(afm-main-plugin) call_appid: %s -> %s\n", sid, obj ? json_object_to_json_string(obj) : "NULL"); + obj = jbus_call_sj_sync(jbus, method, sid); + if (interface->verbose) + fprintf(stderr, "(afm-main-plugin) %s(%s) -> %s\n", method, sid, obj ? json_object_to_json_string(obj) : "NULL"); free(sid); - request->errcode = obj ? MHD_HTTP_OK : MHD_HTTP_FAILED_DEPENDENCY; - return obj; + if (obj == NULL) { + afb_req_fail(request, "failed", "framework daemon failure"); + return; + } + afb_req_success(request, obj, NULL); } -static struct json_object *call_runid(AFB_request *request, AFB_PostItem *item) +static void call_runid(struct afb_req request, const char *method) { struct json_object *obj; - const char *id = getQueryValue(request, _runid_); + const char *id = afb_req_argument(request, _runid_); if (id == NULL) { - request->errcode = MHD_HTTP_BAD_REQUEST; - return NULL; + afb_req_fail(request, "bad-request", "missing 'runid'"); + return; + } + obj = jbus_call_sj_sync(jbus, method, id); + if (interface->verbose) + fprintf(stderr, "(afm-main-plugin) %s(%s) -> %s\n", method, id, + obj ? json_object_to_json_string(obj) : "NULL"); + if (obj == NULL) { + afb_req_fail(request, "failed", "framework daemon failure"); + return; } - obj = jbus_call_sj_sync(jbus, request->method, id); - if (verbose) - fprintf(stderr, "(afm-main-plugin) call_runid: %s -> %s\n", id, obj ? json_object_to_json_string(obj) : "NULL"); - request->errcode = obj ? MHD_HTTP_OK : MHD_HTTP_FAILED_DEPENDENCY; - return obj; + afb_req_success(request, obj, NULL); +} + + +/************************** entries ******************************/ + +static void runnables(struct afb_req request) +{ + embed_call_void(request, _runnables_); } -static struct json_object *call_void__runnables(AFB_request *request, AFB_PostItem *item) +static void detail(struct afb_req request) { - return embed(request, _runnables_, call_void(request, item)); + call_appid(request, _detail_); } -static struct json_object *call_start(AFB_request *request, AFB_PostItem *item) +static void start(struct afb_req request) { - struct json_object *resp; + struct json_object *obj; const char *id, *mode; char *query; int rc; /* get the id */ - id = getQueryValue(request, _id_); + id = afb_req_argument(request, _id_); if (id == NULL) { - request->errcode = MHD_HTTP_BAD_REQUEST; - return NULL; + afb_req_fail(request, "bad-request", "missing 'id'"); + return; } /* get the mode */ - mode = getQueryValue(request, _mode_); + mode = afb_req_argument(request, _mode_); if (mode == NULL || !strcmp(mode, _auto_)) { - mode = request->config->mode == AFB_MODE_REMOTE ? _remote_ : _local_; + mode = interface->mode == AFB_MODE_REMOTE ? _remote_ : _local_; } /* create the query */ rc = asprintf(&query, "{\"id\":\"%s\",\"mode\":\"%s\"}", id, mode); if (rc < 0) { - request->errcode = MHD_HTTP_INTERNAL_SERVER_ERROR; - return NULL; + afb_req_fail(request, "server-error", "out of memory"); + return; } /* calls the service */ - resp = jbus_call_sj_sync(jbus, _start_, query); - if (verbose) - fprintf(stderr, "(afm-main-plugin) call_start: %s -> %s\n", query, resp ? json_object_to_json_string(resp) : "NULL"); + obj = jbus_call_sj_sync(jbus, _start_, query); + if (interface->verbose) + fprintf(stderr, "(afm-main-plugin) start(%s) -> %s\n", query, obj ? json_object_to_json_string(obj) : "NULL"); free(query); + /* check status */ + if (obj == NULL) { + afb_req_fail(request, "failed", "framework daemon failure"); + return; + } + /* embed if needed */ - if (json_object_get_type(resp) == json_type_int) - resp = embed(request, _runid_, resp); - request->errcode = resp ? MHD_HTTP_OK : MHD_HTTP_FAILED_DEPENDENCY; - return resp; + if (json_object_get_type(obj) == json_type_int) + obj = embed(_runid_, obj); + afb_req_success(request, obj, NULL); } -static struct json_object *call_void__runners(AFB_request *request, AFB_PostItem *item) +static void terminate(struct afb_req request) { - return embed(request, _runners_, call_void(request, item)); + call_runid(request, _terminate_); } -static struct json_object *call_file__appid(AFB_request *request, AFB_PostItem *item) -{ - if (item == NULL) { - const char *filename = getPostPath(request); - if (filename != NULL) { - struct json_object *obj; - char *query; - request->jresp = NULL; - if (0 >= asprintf(&query, "\"%s\"", filename)) - request->errcode = MHD_HTTP_INTERNAL_SERVER_ERROR; - else { - obj = jbus_call_sj_sync(jbus, request->method, query); - if (verbose) - fprintf(stderr, "(afm-main-plugin) call_file_appid: %s -> %s\n", query, obj ? json_object_to_json_string(obj) : "NULL"); - free(query); - if (obj) - request->jresp = embed(request, _id_, obj); - else - request->errcode = MHD_HTTP_FAILED_DEPENDENCY; - } - unlink(filename); - } +static void stop(struct afb_req request) +{ + call_runid(request, _stop_); +} + +static void continue_(struct afb_req request) +{ + call_runid(request, _continue_); +} + +static void runners(struct afb_req request) +{ + embed_call_void(request, _runners_); +} + +static void state(struct afb_req request) +{ + call_runid(request, _state_); +} + +static void install(struct afb_req request) +{ + struct json_object *obj; + char *query; + const char *filename; + struct afb_arg arg; + + /* get the argument */ + arg = afb_req_get(request, "widget"); + filename = arg.value; + if (filename == NULL || !arg.is_file) { + afb_req_fail(request, "bad-request", "missing 'widget' file"); + return; + } + + /* makes the query */ + if (0 >= asprintf(&query, "\"%s\"", filename)) { + afb_req_fail(request, "server-error", "out of memory"); + return; } - return getPostFile (request, item, "/tmp/upload"); + + obj = jbus_call_sj_sync(jbus, _install_, query); + if (interface->verbose) + fprintf(stderr, "(afm-main-plugin) install(%s) -> %s\n", query, obj ? json_object_to_json_string(obj) : "NULL"); + free(query); + + /* check status */ + if (obj == NULL) { + afb_req_fail(request, "failed", "framework daemon failure"); + return; + } + + /* embed if needed */ + obj = embed(_id_, obj); + afb_req_success(request, obj, NULL); } -static AFB_restapi plug_apis[] = -{ - {_runnables_, AFB_SESSION_CHECK, (AFB_apiCB)call_void__runnables, "Get list of runnable applications"}, - {_detail_ , AFB_SESSION_CHECK, (AFB_apiCB)call_appid, "Get the details for one application"}, - {_start_ , AFB_SESSION_CHECK, (AFB_apiCB)call_start, "Start an application"}, - {_terminate_, AFB_SESSION_CHECK, (AFB_apiCB)call_runid, "Terminate a running application"}, - {_stop_ , AFB_SESSION_CHECK, (AFB_apiCB)call_runid, "Stop (pause) a running application"}, - {_continue_ , AFB_SESSION_CHECK, (AFB_apiCB)call_runid, "Continue (resume) a stopped application"}, - {_runners_ , AFB_SESSION_CHECK, (AFB_apiCB)call_void__runners, "Get the list of running applications"}, - {_state_ , AFB_SESSION_CHECK, (AFB_apiCB)call_runid, "Get the state of a running application"}, - {_install_ , AFB_SESSION_CHECK, (AFB_apiCB)call_file__appid, "Install an application using a widget file"}, - {_uninstall_, AFB_SESSION_CHECK, (AFB_apiCB)call_appid, "Uninstall an application"}, +static void uninstall(struct afb_req request) +{ + call_appid(request, _uninstall_); +} + +static const struct AFB_restapi plug_apis[] = +{ + {_runnables_, AFB_SESSION_CHECK, runnables, "Get list of runnable applications"}, + {_detail_ , AFB_SESSION_CHECK, detail, "Get the details for one application"}, + {_start_ , AFB_SESSION_CHECK, start, "Start an application"}, + {_terminate_, AFB_SESSION_CHECK, terminate, "Terminate a running application"}, + {_stop_ , AFB_SESSION_CHECK, stop, "Stop (pause) a running application"}, + {_continue_ , AFB_SESSION_CHECK, continue_, "Continue (resume) a stopped application"}, + {_runners_ , AFB_SESSION_CHECK, runners, "Get the list of running applications"}, + {_state_ , AFB_SESSION_CHECK, state, "Get the state of a running application"}, + {_install_ , AFB_SESSION_CHECK, install, "Install an application using a widget file"}, + {_uninstall_, AFB_SESSION_CHECK, uninstall, "Uninstall an application"}, { NULL, 0, NULL, NULL } }; -static AFB_plugin plug_desc = { +static const struct AFB_plugin plug_desc = { .type = AFB_PLUGIN_JSON, .info = "Application Framework Master Service", .prefix = "afm-main", .apis = plug_apis }; -AFB_plugin *pluginRegister() +const struct AFB_plugin *pluginRegister(const struct AFB_interface *itf) { + interface = itf; + jbus = create_jbus_session("/org/AGL/afm/user"); if (jbus) return &plug_desc; diff --git a/plugins/samples/ClientCtx.c b/plugins/samples/ClientCtx.c index cda6abbb..3fa43adf 100644 --- a/plugins/samples/ClientCtx.c +++ b/plugins/samples/ClientCtx.c @@ -75,7 +75,7 @@ static void myCreate (struct afb_req request) ctx->count = 0; ctx->abcd = "SomeThingUseful"; - *request.context = ctx; + request.context = ctx; afb_req_success_f(request, NULL, "SUCCESS: create client context for plugin [%s]", handle->anythingYouWant); } @@ -86,7 +86,7 @@ static void myCreate (struct afb_req request) static void myAction (struct afb_req request) { MyPluginHandleT *handle = (MyPluginHandleT*) &global_handle; - MyClientContextT *ctx = (MyClientContextT*) *request.context; + MyClientContextT *ctx = (MyClientContextT*) request.context; // store something in our plugin private client context ctx->count++; @@ -100,7 +100,7 @@ static void myAction (struct afb_req request) static void myClose (struct afb_req request) { MyPluginHandleT *handle = (MyPluginHandleT*) &global_handle; - MyClientContextT *ctx = (MyClientContextT*) *request.context; + MyClientContextT *ctx = (MyClientContextT*) request.context; // store something in our plugin private client context ctx->count++; @@ -132,8 +132,9 @@ static const struct AFB_plugin plugin_desc = { .freeCtxCB = (void*)freeCtxCB }; -const struct AFB_plugin *pluginRegister () +const struct AFB_plugin *pluginRegister (const struct AFB_interface *itf) { + global_handle.anythingYouWant = "anythingYouWant"; return &plugin_desc; } diff --git a/plugins/samples/HelloWorld.c b/plugins/samples/HelloWorld.c index 4f0af71f..c60cc70c 100644 --- a/plugins/samples/HelloWorld.c +++ b/plugins/samples/HelloWorld.c @@ -85,7 +85,6 @@ static void pingBug (struct afb_req request) { int a,b,c; - fprintf (stderr, "Use --timeout=10 to trap error\n"); b=4; c=0; a=b/c; @@ -128,7 +127,7 @@ static const struct AFB_plugin plugin_desc = { .apis = pluginApis }; -const struct AFB_plugin *pluginRegister () +const struct AFB_plugin *pluginRegister (const struct AFB_interface *itf) { return &plugin_desc; } diff --git a/plugins/session/token-api.c b/plugins/session/token-api.c index 65c18d26..04945a80 100644 --- a/plugins/session/token-api.c +++ b/plugins/session/token-api.c @@ -36,7 +36,7 @@ static void clientContextCreate (struct afb_req request) json_object *jresp; // add an application specific client context to session - *request.context = malloc (sizeof (MyClientApplicationHandle)); + request.context = malloc (sizeof (MyClientApplicationHandle)); // Send response to UI jresp = json_object_new_object(); @@ -118,7 +118,7 @@ static const struct AFB_plugin plugin_desc = { .freeCtxCB = clientContextFree }; -const struct AFB_plugin *pluginRegister () +const struct AFB_plugin *pluginRegister (const struct AFB_interface *itf) { return &plugin_desc; } diff --git a/src/afb-apis.c b/src/afb-apis.c index 0a04ed73..735649ca 100644 --- a/src/afb-apis.c +++ b/src/afb-apis.c @@ -41,13 +41,15 @@ #include "afb-plugin.h" #include "afb-req-itf.h" +#include "session.h" #include "afb-apis.h" struct api_desc { - AFB_plugin *plugin; /* descriptor */ + struct AFB_plugin *plugin; /* descriptor */ size_t prefixlen; const char *prefix; void *handle; /* context of dlopen */ + struct AFB_interface *interface; }; static int api_timeout = 15; @@ -73,50 +75,14 @@ void afb_apis_free_context(int apiidx, void *context) free(context); } -/* -const struct AFB_restapi *afb_apis_get(int apiidx, int verbidx) -{ - assert(0 <= apiidx && apiidx < apis_count); - return &apis_array[apiidx].plugin->apis[verbidx]; -} - -int afb_apis_get_verbidx(int apiidx, const char *name) -{ - const struct AFB_restapi *apis; - int idx; - - assert(0 <= apiidx && apiidx < apis_count); - apis = apis_array[apiidx].plugin->apis; - for (idx = 0 ; apis[idx].name ; idx++) - if (!strcasecmp(apis[idx].name, name)) - return idx; - return -1; -} -*/ - -int afb_apis_get_apiidx(const char *prefix, size_t length) -{ - int i; - const struct api_desc *a; - - if (!length) - length = strlen(prefix); - - for (i = 0 ; i < apis_count ; i++) { - a = &apis_array[i]; - if (a->prefixlen == length && !strcasecmp(a->prefix, prefix)) - return i; - } - return -1; -} - int afb_apis_add_plugin(const char *path) { struct api_desc *apis; - AFB_plugin *plugin; - AFB_plugin *(*pluginRegisterFct) (void); + struct AFB_plugin *plugin; + struct AFB_plugin *(*pluginRegisterFct) (const struct AFB_interface *interface); + struct AFB_interface *interface; void *handle; - size_t len; + int i; // This is a loadable library let's check if it's a plugin handle = dlopen(path, RTLD_NOW | RTLD_LOCAL); @@ -142,36 +108,46 @@ int afb_apis_add_plugin(const char *path) } apis_array = apis; + /* allocates the interface */ + interface = calloc(1, sizeof *interface); + if (interface == NULL) { + fprintf(stderr, "ERROR: plugin [%s] memory missing. continuing...\n", path); + goto error2; + } + interface->verbose = 0; + interface->mode = AFB_MODE_LOCAL; + /* init the plugin */ - plugin = pluginRegisterFct(); + plugin = pluginRegisterFct(interface); if (plugin == NULL) { fprintf(stderr, "ERROR: plugin [%s] register function failed. continuing...\n", path); - goto error2; + goto error3; } /* check the returned structure */ if (plugin->type != AFB_PLUGIN_JSON) { fprintf(stderr, "ERROR: plugin [%s] invalid type %d...\n", path, plugin->type); - goto error2; + goto error3; } if (plugin->prefix == NULL || *plugin->prefix == 0) { fprintf(stderr, "ERROR: plugin [%s] bad prefix...\n", path); - goto error2; + goto error3; } if (plugin->info == NULL || *plugin->info == 0) { fprintf(stderr, "ERROR: plugin [%s] bad description...\n", path); - goto error2; + goto error3; } if (plugin->apis == NULL) { fprintf(stderr, "ERROR: plugin [%s] no APIs...\n", path); - goto error2; + goto error3; } /* check previously existing plugin */ - len = strlen(plugin->prefix); - if (afb_apis_get_apiidx(plugin->prefix, len) >= 0) { - fprintf(stderr, "ERROR: plugin [%s] prefix %s duplicated...\n", path, plugin->prefix); - goto error2; + for (i = 0 ; i < apis_count ; i++) { + if (!strcasecmp(apis_array[i].prefix, plugin->prefix)) { + fprintf(stderr, "ERROR: plugin [%s] prefix %s duplicated...\n", path, plugin->prefix); + goto error2; + } } /* record the plugin */ @@ -179,13 +155,16 @@ int afb_apis_add_plugin(const char *path) fprintf(stderr, "Loading plugin[%lu] prefix=[%s] info=%s\n", (unsigned long)apis_count, plugin->prefix, plugin->info); apis = &apis_array[apis_count]; apis->plugin = plugin; - apis->prefixlen = len; + apis->prefixlen = strlen(plugin->prefix); apis->prefix = plugin->prefix; apis->handle = handle; + apis->interface = interface; apis_count++; return 0; +error3: + free(interface); error2: dlclose(handle); error: @@ -330,46 +309,33 @@ static void trapping_handle(struct afb_req req, void(*cb)(struct afb_req)) error_handler = older; } -static void handle(struct afb_req req, int idxapi, const struct AFB_restapi *verb) +static void handle(struct afb_req req, const struct AFB_restapi *verb) { switch(verb->session) { case AFB_SESSION_CREATE: - /* - req.context = afb_req_session_create(req, idxapi); - if (req.context == NULL) + if (!afb_req_session_create(req)) return; break; - */ case AFB_SESSION_RENEW: - /* - req.context = afb_req_session_check(req, idxapi, 1); - if (req.context == NULL) + if (!afb_req_session_check(req, 1)) return; - */ break; case AFB_SESSION_CLOSE: case AFB_SESSION_CHECK: - /* - req.context = afb_req_session_check(req, idxapi, 1); - if (req.context == NULL) + if (!afb_req_session_check(req, 0)) return; - */ break; case AFB_SESSION_NONE: default: - req.context = NULL; break; } trapping_handle(req, verb->callback); - if (verb->session == AFB_SESSION_CLOSE) { - /* + if (verb->session == AFB_SESSION_CLOSE) afb_req_session_close(req); - */ - } } -int afb_apis_handle(struct afb_req req, const char *api, size_t lenapi, const char *verb, size_t lenverb) +int afb_apis_handle(struct afb_req req, struct AFB_clientCtx *context, const char *api, size_t lenapi, const char *verb, size_t lenverb) { int i, j; const struct api_desc *a; @@ -381,7 +347,9 @@ int afb_apis_handle(struct afb_req req, const char *api, size_t lenapi, const ch v = a->plugin->apis; for (j = 0 ; v->name ; j++, v++) { if (!strncasecmp(v->name, verb, lenverb) && !v->name[lenverb]) { - handle(req, i, v); + req.context = context->contexts[i]; + handle(req, v); + context->contexts[i] = req.context; return 1; } } diff --git a/src/afb-apis.h b/src/afb-apis.h index dc4401cf..9127e6d8 100644 --- a/src/afb-apis.h +++ b/src/afb-apis.h @@ -20,12 +20,6 @@ extern int afb_apis_count(); extern void afb_apis_free_context(int apiidx, void *context); -extern const struct AFB_restapi *afb_apis_get(int apiidx, int verbidx); - -extern int afb_apis_get_verbidx(int apiidx, const char *name); - -extern int afb_apis_get_apiidx(const char *prefix, size_t length); - extern int afb_apis_add_plugin(const char *path); extern int afb_apis_add_directory(const char *path); @@ -35,5 +29,6 @@ extern int afb_apis_add_path(const char *path); extern int afb_apis_add_pathset(const char *pathset); struct afb_req; -extern int afb_apis_handle(struct afb_req req, const char *api, size_t lenapi, const char *verb, size_t lenverb); +struct AFB_clientCtx; +extern int afb_apis_handle(struct afb_req req, struct AFB_clientCtx *context, const char *api, size_t lenapi, const char *verb, size_t lenverb); diff --git a/src/afb-hreq.c b/src/afb-hreq.c index e146bcb4..16c235ab 100644 --- a/src/afb-hreq.c +++ b/src/afb-hreq.c @@ -31,11 +31,21 @@ #include "afb-method.h" #include "afb-req-itf.h" #include "afb-hreq.h" +#include "session.h" #define SIZE_RESPONSE_BUFFER 8000 static char empty_string[] = ""; +static const char uuid_header[] = "x-afb-uuid"; +static const char uuid_arg[] = "uuid"; +static const char uuid_cookie[] = "uuid"; + +static const char token_header[] = "x-afb-token"; +static const char token_arg[] = "token"; +static const char token_cookie[] = "token"; + + struct hreq_data { struct hreq_data *next; char *key; @@ -48,15 +58,19 @@ static struct afb_arg req_get(struct afb_hreq *hreq, const char *name); static void req_iterate(struct afb_hreq *hreq, int (*iterator)(void *closure, struct afb_arg arg), void *closure); static void req_fail(struct afb_hreq *hreq, const char *status, const char *info); static void req_success(struct afb_hreq *hreq, json_object *obj, const char *info); +static int req_session_create(struct afb_hreq *hreq); +static int req_session_check(struct afb_hreq *hreq, int refresh); +static void req_session_close(struct afb_hreq *hreq); static const struct afb_req_itf afb_hreq_itf = { .get = (void*)req_get, .iterate = (void*)req_iterate, .fail = (void*)req_fail, - .success = (void*)req_success + .success = (void*)req_success, + .session_create = (void*)req_session_create, + .session_check = (void*)req_session_check, + .session_close = (void*)req_session_close }; - void (*fail)(void *data, const char *status, const char *info); - void (*success)(void *data, json_object *obj, const char *info); static struct hreq_data *get_data(struct afb_hreq *hreq, const char *key, int create) { @@ -121,8 +135,25 @@ static int validsubpath(const char *subpath) return 1; } +void afb_hreq_free(struct afb_hreq *hreq) +{ + struct hreq_data *data; + if (hreq != NULL) { + if (hreq->postform != NULL) + MHD_destroy_post_processor(hreq->postform); + for (data = hreq->data; data; data = hreq->data) { + hreq->data = data->next; + free(data->key); + free(data->value); + free(data); + } + ctxClientPut(hreq->context); + free(hreq); + } +} + /* - * Removes the 'prefix' of 'length' frome the tail of 'hreq' + * Removes the 'prefix' of 'length' from the tail of 'hreq' * if and only if the prefix exists and is terminated by a leading * slash */ @@ -430,17 +461,6 @@ static void req_iterate(struct afb_hreq *hreq, int (*iterator)(void *closure, st MHD_get_connection_values (hreq->connection, MHD_GET_ARGUMENT_KIND, (void*)_iterargs_, &id); } -void afb_hreq_drop_data(struct afb_hreq *hreq) -{ - struct hreq_data *data = hreq->data; - while (data) { - hreq->data = data->next; - free(data->key); - free(data->value); - free(data); - data = hreq->data; - } -} static ssize_t send_json_cb(json_object *obj, uint64_t pos, char *buf, size_t max) { ssize_t len = stpncpy(buf, json_object_to_json_string(obj)+pos, max) - buf; @@ -461,6 +481,10 @@ static void req_reply(struct afb_hreq *hreq, unsigned retcode, const char *statu json_object_object_add(request, "info", json_object_new_string(info)); if (resp) json_object_object_add(root, "response", resp); + if (hreq->context) { + json_object_object_add(request, uuid_arg, json_object_new_string(hreq->context->uuid)); + json_object_object_add(request, token_arg, json_object_new_string(hreq->context->token)); + } response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, SIZE_RESPONSE_BUFFER, (void*)send_json_cb, root, (void*)json_object_put); MHD_queue_response(hreq->connection, retcode, response); @@ -477,3 +501,64 @@ static void req_success(struct afb_hreq *hreq, json_object *obj, const char *inf req_reply(hreq, MHD_HTTP_OK, "success", info, obj); } +struct AFB_clientCtx *afb_hreq_context(struct afb_hreq *hreq) +{ + const char *uuid; + + if (hreq->context == NULL) { + uuid = afb_hreq_get_header(hreq, uuid_header); + if (uuid == NULL) + uuid = afb_hreq_get_argument(hreq, uuid_arg); + if (uuid == NULL) + uuid = afb_hreq_get_cookie(hreq, uuid_cookie); + hreq->context = ctxClientGet(uuid); + } + return hreq->context; +} + +static int req_session_create(struct afb_hreq *hreq) +{ + struct AFB_clientCtx *context = afb_hreq_context(hreq); + if (context == NULL) + return 0; + if (context->created) + return 0; + return req_session_check(hreq, 1); +} + +static int req_session_check(struct afb_hreq *hreq, int refresh) +{ + const char *token; + + struct AFB_clientCtx *context = afb_hreq_context(hreq); + + if (context == NULL) + return 0; + + token = afb_hreq_get_header(hreq, token_header); + if (token == NULL) + token = afb_hreq_get_argument(hreq, token_arg); + if (token == NULL) + token = afb_hreq_get_cookie(hreq, token_cookie); + if (token == NULL) + return 0; + + if (!ctxTokenCheck (context, token)) + return 0; + + if (refresh) { + ctxTokenNew (context); + } + + return 1; +} + +static void req_session_close(struct afb_hreq *hreq) +{ + struct AFB_clientCtx *context = afb_hreq_context(hreq); + if (context != NULL) + ctxClientClose(context); +} + + + diff --git a/src/afb-hreq.h b/src/afb-hreq.h index fb914f13..cf236380 100644 --- a/src/afb-hreq.h +++ b/src/afb-hreq.h @@ -16,6 +16,7 @@ */ struct AFB_session; +struct AFB_clientCtx; struct afb_hreq { struct AFB_session *session; @@ -31,6 +32,8 @@ struct afb_hreq { struct hreq_data *data; }; +extern void afb_hreq_free(struct afb_hreq *request); + extern int afb_hreq_unprefix(struct afb_hreq *request, const char *prefix, size_t length); extern int afb_hreq_valid_tail(struct afb_hreq *request); @@ -59,5 +62,4 @@ extern void afb_hreq_post_end(struct afb_hreq *hreq); extern struct afb_req afb_hreq_to_req(struct afb_hreq *hreq); -extern void afb_hreq_drop_data(struct afb_hreq *hreq); - +extern struct AFB_clientCtx *afb_hreq_context(struct afb_hreq *hreq); diff --git a/src/http-svc.c b/src/http-svc.c index 9235bf87..c767de71 100644 --- a/src/http-svc.c +++ b/src/http-svc.c @@ -31,12 +31,12 @@ #include "afb-hreq.h" #include "afb-websock.h" #include "afb-apis.h" -#include "session.h" #include "afb-req-itf.h" #define JSON_CONTENT "application/json" #define FORM_CONTENT MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA + struct afb_hsrv_handler { struct afb_hsrv_handler *next; const char *prefix; @@ -137,25 +137,6 @@ int afb_hsrv_add_handler( return 1; } -static const char uuid_header[] = "x-afb-uuid"; -static const char uuid_arg[] = "uuid"; -static const char uuid_cookie[] = "uuid"; - -static struct AFB_clientCtx *afb_hreq_context(struct afb_hreq *hreq) -{ - const char *uuid; - - if (hreq->context == NULL) { - uuid = afb_hreq_get_header(hreq, uuid_header); - if (uuid == NULL) - uuid = afb_hreq_get_argument(hreq, uuid_arg); - if (uuid == NULL) - uuid = afb_hreq_get_cookie(hreq, uuid_cookie); - hreq->context = ctxClientGet(uuid); - } - return hreq->context; -} - static int afb_hreq_websocket_switch(struct afb_hreq *hreq, void *data) { int later; @@ -179,6 +160,7 @@ static int afb_hreq_rest_api(struct afb_hreq *hreq, void *data) { const char *api, *verb; size_t lenapi, lenverb; + struct AFB_clientCtx *context; api = &hreq->tail[strspn(hreq->tail, "/")]; lenapi = strcspn(api, "/"); @@ -189,7 +171,8 @@ static int afb_hreq_rest_api(struct afb_hreq *hreq, void *data) if (!(*api && *verb && lenapi && lenverb)) return 0; - return afb_apis_handle(afb_hreq_to_req(hreq), api, lenapi, verb, lenverb); + context = afb_hreq_context(hreq); + return afb_apis_handle(afb_hreq_to_req(hreq), context, api, lenapi, verb, lenverb); } static int handle_alias(struct afb_hreq *hreq, void *data) @@ -376,18 +359,11 @@ internal_error: static void end_handler(void *cls, struct MHD_Connection *connection, void **recordreq, enum MHD_RequestTerminationCode toe) { - AFB_session *session; struct afb_hreq *hreq; - session = cls; hreq = *recordreq; - if (hreq != NULL) { - if (hreq->postform != NULL) - MHD_destroy_post_processor(hreq->postform); - afb_hreq_drop_data(hreq); - free(hreq); - } + afb_hreq_free(hreq); } static int new_client_handler(void *cls, const struct sockaddr *addr, socklen_t addrlen) diff --git a/src/local-def.h b/src/local-def.h index a1cac538..39ae3181 100644 --- a/src/local-def.h +++ b/src/local-def.h @@ -66,62 +66,11 @@ typedef enum { AFB_FALSE, AFB_TRUE, AFB_FATAL, AFB_FAIL, AFB_WARNING, AFB_EMPTY -#if 0 - -// Plugin Type -enum AFB_pluginE -{ - AFB_PLUGIN_JSON = 123456789, - AFB_PLUGIN_JSCRIPT = 987654321, - AFB_PLUGIN_RAW = 987123546 -}; - -// Enum for Session/Token/Authentication middleware -enum AFB_sessionE -{ - AFB_SESSION_NONE, - AFB_SESSION_CREATE, - AFB_SESSION_CLOSE, - AFB_SESSION_RENEW, - AFB_SESSION_CHECK -}; - -// API definition -struct AFB_restapi -{ - const char *name; - enum AFB_sessionE session; - struct json_object* (*callback)(); - const char *info; -}; - -// Plugin definition -struct AFB_plugin -{ - enum AFB_pluginE type; - const char *info; - const char *prefix; - const struct AFB_restapi *apis; - void (*freeCtxCB)(void*); // callback to free application context [null for standard free] -}; - -typedef enum AFB_pluginE AFB_pluginE; -typedef enum AFB_sessionE AFB_sessionE; -typedef struct json_object* (*AFB_apiCB)(); -typedef void (*AFB_freeCtxCB)(void*); -typedef struct AFB_restapi AFB_restapi; -typedef struct AFB_plugin AFB_plugin; - - -#endif - - - -typedef enum {AFB_MODE_LOCAL=0, AFB_MODE_REMOTE, AFB_MODE_GLOBAL} AFB_Mode; +enum AFB_Mode; typedef struct { @@ -144,7 +93,7 @@ struct AFB_config int cacheTimeout; int apiTimeout; int cntxTimeout; // Client Session Context timeout - AFB_Mode mode; // mode of listening + int mode; // mode of listening AFB_aliasdir *aliasdir; // alias mapping for icons,apps,... }; diff --git a/src/main.c b/src/main.c index 2401043c..95b5a653 100644 --- a/src/main.c +++ b/src/main.c @@ -27,10 +27,7 @@ #include #include -/* -#include -#include -*/ +#include "afb-plugin.h" #include "local-def.h" #include "afb-apis.h" diff --git a/src/session.c b/src/session.c index ad411239..e9cf298e 100644 --- a/src/session.c +++ b/src/session.c @@ -29,16 +29,6 @@ #include #include - -/* -#include -#include -#include -#include -#include -#include -*/ - #include "afb-apis.h" #include "session.h" @@ -47,7 +37,7 @@ // Session UUID are store in a simple array [for 10 sessions this should be enough] static struct { pthread_mutex_t mutex; // declare a mutex to protect hash table - AFB_clientCtx **store; // sessions store + struct AFB_clientCtx **store; // sessions store int count; // current number of sessions int max; int timeout; @@ -55,31 +45,28 @@ static struct { const char *initok; } sessions; -static const char key_uuid[] = "uuid"; -static const char key_token[] = "token"; - // Free context [XXXX Should be protected again memory abort XXXX] -static void ctxUuidFreeCB (AFB_clientCtx *client) +static void ctxUuidFreeCB (struct AFB_clientCtx *client) { - int idx; + int idx; - // If application add a handle let's free it now - if (client->contexts != NULL) { + // If application add a handle let's free it now + assert (client->contexts != NULL); - // Free client handle with a standard Free function, with app callback or ignore it - for (idx=0; idx < sessions.apicount; idx ++) { - if (client->contexts[idx] != NULL) { - afb_apis_free_context(idx, client->contexts[idx]); - } - } - } + // Free client handle with a standard Free function, with app callback or ignore it + for (idx=0; idx < sessions.apicount; idx ++) { + if (client->contexts[idx] != NULL) { + afb_apis_free_context(idx, client->contexts[idx]); + client->contexts[idx] = NULL; + } + } } // Create a new store in RAM, not that is too small it will be automatically extended void ctxStoreInit (int nbSession, int timeout, int apicount, const char *initok) { // let's create as store as hashtable does not have any - sessions.store = calloc (1 + (unsigned)nbSession, sizeof(AFB_clientCtx)); + sessions.store = calloc (1 + (unsigned)nbSession, sizeof(struct AFB_clientCtx)); sessions.max = nbSession; sessions.timeout = timeout; sessions.apicount = apicount; @@ -90,10 +77,10 @@ void ctxStoreInit (int nbSession, int timeout, int apicount, const char *initok) sessions.initok = initok; } -static AFB_clientCtx *ctxStoreSearch (const char* uuid) +static struct AFB_clientCtx *ctxStoreSearch (const char* uuid) { int idx; - AFB_clientCtx *client; + struct AFB_clientCtx *client; assert (uuid != NULL); @@ -111,7 +98,7 @@ found: return client; } -static int ctxStoreDel (AFB_clientCtx *client) +static int ctxStoreDel (struct AFB_clientCtx *client) { int idx; int status; @@ -124,7 +111,6 @@ static int ctxStoreDel (AFB_clientCtx *client) if (sessions.store[idx] == client) { sessions.store[idx]=NULL; sessions.count--; - ctxUuidFreeCB (client); status = 1; goto deleted; } @@ -135,7 +121,7 @@ deleted: return status; } -static int ctxStoreAdd (AFB_clientCtx *client) +static int ctxStoreAdd (struct AFB_clientCtx *client) { int idx; int status; @@ -161,59 +147,65 @@ added: } // Check if context timeout or not -static int ctxStoreTooOld (AFB_clientCtx *ctx, time_t now) +static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now) { - return ctx->timeStamp <= now; + return ctx->expiration <= now; } // Loop on every entry and remove old context sessions.hash -void ctxStoreGarbage () +static void ctxStoreCleanUp (time_t now) { - AFB_clientCtx *ctx; - long idx; - time_t now = NOW; - - // Loop on Sessions Table and remove anything that is older than timeout - for (idx=0; idx < sessions.max; idx++) { - ctx = sessions.store[idx]; - if (ctx != NULL && ctxStoreTooOld(ctx, now)) { - ctxStoreDel (ctx); - } - } + struct AFB_clientCtx *ctx; + long idx; + + // Loop on Sessions Table and remove anything that is older than timeout + for (idx=0; idx < sessions.max; idx++) { + ctx = sessions.store[idx]; + if (ctx != NULL && ctxStoreTooOld(ctx, now)) { + ctxClientClose (ctx); + } + } } // This function will return exiting client context or newly created client context -AFB_clientCtx *ctxClientGet (const char *uuid) +struct AFB_clientCtx *ctxClientGet (const char *uuid) { uuid_t newuuid; - AFB_clientCtx *clientCtx; + struct AFB_clientCtx *clientCtx; + time_t now; /* search for an existing one not too old */ + now = NOW; + ctxStoreCleanUp (now); clientCtx = uuid != NULL ? ctxStoreSearch (uuid) : NULL; if (clientCtx) { - if (!ctxStoreTooOld (clientCtx, NOW)) + clientCtx->refcount++; return clientCtx; - ctxStoreDel (clientCtx); } /* mimic old behaviour */ if (sessions.initok == NULL) return NULL; - /* cleanup before creating */ - if(2 * sessions.count >= sessions.max) - ctxStoreGarbage(); + /* check the uuid if given */ + if (uuid != NULL && 1 + strlen(uuid) >= sizeof clientCtx->uuid) + return NULL; /* returns a new one */ - clientCtx = calloc(1, sizeof(AFB_clientCtx)); // init NULL clientContext + clientCtx = calloc(1, sizeof(struct AFB_clientCtx)); // init NULL clientContext if (clientCtx != NULL) { clientCtx->contexts = calloc ((unsigned)sessions.apicount, sizeof (void*)); if (clientCtx->contexts != NULL) { /* generate the uuid */ - uuid_generate(newuuid); - uuid_unparse_lower(newuuid, clientCtx->uuid); - clientCtx->timeStamp = time(NULL) + sessions.timeout; + if (uuid == NULL) { + uuid_generate(newuuid); + uuid_unparse_lower(newuuid, clientCtx->uuid); + } else { + strcpy(clientCtx->uuid, uuid); + } strcpy(clientCtx->token, sessions.initok); + clientCtx->expiration = now + sessions.timeout; + clientCtx->refcount = 1; if (ctxStoreAdd (clientCtx)) return clientCtx; free(clientCtx->contexts); @@ -223,15 +215,28 @@ AFB_clientCtx *ctxClientGet (const char *uuid) return NULL; } +void ctxClientPut(struct AFB_clientCtx *clientCtx) +{ + if (clientCtx != NULL) { + assert(clientCtx->refcount != 0); + --clientCtx->refcount; + } +} + // Free Client Session Context -int ctxClientClose (AFB_clientCtx *clientCtx) +void ctxClientClose (struct AFB_clientCtx *clientCtx) { assert(clientCtx != NULL); - return ctxStoreDel (clientCtx); + if (clientCtx->created) { + clientCtx->created = 0; + ctxUuidFreeCB (clientCtx); + } + if (clientCtx->refcount == 0) + ctxStoreDel (clientCtx); } // Sample Generic Ping Debug API -int ctxTokenCheck (AFB_clientCtx *clientCtx, const char *token) +int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token) { assert(clientCtx != NULL); assert(token != NULL); @@ -239,8 +244,9 @@ int ctxTokenCheck (AFB_clientCtx *clientCtx, const char *token) // compare current token with previous one if (ctxStoreTooOld (clientCtx, NOW)) return 0; + if (!clientCtx->token[0] || 0 == strcmp (token, clientCtx->token)) { - clientCtx->timeStamp = time(NULL) + sessions.timeout; + clientCtx->created = 1; /* creates by default */ return 1; } @@ -249,7 +255,7 @@ int ctxTokenCheck (AFB_clientCtx *clientCtx, const char *token) } // generate a new token and update client context -int ctxTokenNew (AFB_clientCtx *clientCtx) +void ctxTokenNew (struct AFB_clientCtx *clientCtx) { uuid_t newuuid; @@ -260,8 +266,6 @@ int ctxTokenNew (AFB_clientCtx *clientCtx) uuid_unparse_lower(newuuid, clientCtx->token); // keep track of time for session timeout and further clean up - clientCtx->timeStamp = time(NULL) + sessions.timeout; - - return 1; + clientCtx->expiration = NOW + sessions.timeout; } diff --git a/src/session.h b/src/session.h index 9b54179b..28f25991 100644 --- a/src/session.h +++ b/src/session.h @@ -18,19 +18,19 @@ struct AFB_clientCtx { - time_t timeStamp; // last time token was refresh - void **contexts; // application specific context [one per plugin]] - char uuid[37]; // long term authentication of remote client - char token[37]; // short term authentication of remote client + time_t expiration; // expiration time of the token + int created; + unsigned refcount; + void **contexts; // application specific context [one per plugin] + char uuid[37]; // long term authentication of remote client + char token[37]; // short term authentication of remote client }; -typedef struct AFB_clientCtx AFB_clientCtx; - -extern void ctxStoreGarbage (); extern void ctxStoreInit (int nbSession, int timeout, int apicount, const char *initok); -extern AFB_clientCtx *ctxClientGet (const char *uuid); -extern int ctxClientClose (AFB_clientCtx *clientCtx); -extern int ctxTokenCheck (AFB_clientCtx *clientCtx, const char *token); -extern int ctxTokenNew (AFB_clientCtx *clientCtx); +extern struct AFB_clientCtx *ctxClientGet (const char *uuid); +extern void ctxClientPut(struct AFB_clientCtx *clientCtx); +extern void ctxClientClose (struct AFB_clientCtx *clientCtx); +extern int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token); +extern void ctxTokenNew (struct AFB_clientCtx *clientCtx); -- 2.16.6