2 * Copyright (C) 2015, 2016 "IoT.bzh"
3 * Author "Fulup Ar Foll"
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
24 #include <uuid/uuid.h>
28 #include <json-c/json.h>
33 #define NOW (time(NULL))
38 void (*free_value)(void*);
41 struct afb_event_listener_list
43 struct afb_event_listener_list *next;
44 struct afb_event_listener listener;
52 time_t expiration; // expiration time of the token
54 char uuid[37]; // long term authentication of remote client
55 char token[37]; // short term authentication of remote client
56 struct client_value *values;
57 struct afb_event_listener_list *listeners;
60 // Session UUID are store in a simple array [for 10 sessions this should be enough]
62 pthread_mutex_t mutex; // declare a mutex to protect hash table
63 struct AFB_clientCtx **store; // sessions store
64 int count; // current number of sessions
69 struct afb_event_listener_list *listeners;
73 static void new_uuid(char uuid[37])
76 uuid_generate(newuuid);
77 uuid_unparse_lower(newuuid, uuid);
80 // Free context [XXXX Should be protected again memory abort XXXX]
81 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
85 // If application add a handle let's free it now
86 assert (client->values != NULL);
88 // Free client handle with a standard Free function, with app callback or ignore it
89 for (idx=0; idx < sessions.apicount; idx ++)
90 ctxClientValueSet(client, idx, NULL, NULL);
93 // Create a new store in RAM, not that is too small it will be automatically extended
94 void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count)
96 // let's create as store as hashtable does not have any
97 sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx));
98 sessions.max = max_session_count;
99 sessions.timeout = timeout;
100 sessions.apicount = context_count;
102 /* without token, a secret is made to forbid creation of sessions */
103 new_uuid(sessions.initok);
104 else if (strlen(initok) < sizeof(sessions.store[0]->token))
105 strcpy(sessions.initok, initok);
107 ERROR("initial token '%s' too long (max length 36)", initok);
112 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
115 struct AFB_clientCtx *client;
117 assert (uuid != NULL);
119 pthread_mutex_lock(&sessions.mutex);
121 for (idx=0; idx < sessions.max; idx++) {
122 client = sessions.store[idx];
123 if (client && (0 == strcmp (uuid, client->uuid)))
129 pthread_mutex_unlock(&sessions.mutex);
133 static int ctxStoreDel (struct AFB_clientCtx *client)
138 assert (client != NULL);
140 pthread_mutex_lock(&sessions.mutex);
142 for (idx=0; idx < sessions.max; idx++) {
143 if (sessions.store[idx] == client) {
144 sessions.store[idx] = NULL;
152 pthread_mutex_unlock(&sessions.mutex);
156 static int ctxStoreAdd (struct AFB_clientCtx *client)
161 assert (client != NULL);
163 pthread_mutex_lock(&sessions.mutex);
165 for (idx=0; idx < sessions.max; idx++) {
166 if (NULL == sessions.store[idx]) {
167 sessions.store[idx] = client;
175 pthread_mutex_unlock(&sessions.mutex);
179 // Check if context timeout or not
180 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
182 assert (ctx != NULL);
183 return ctx->expiration < now;
186 // Check if context is active or not
187 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
189 assert (ctx != NULL);
190 return ctx->uuid[0] != 0 && ctx->expiration >= now;
193 // Loop on every entry and remove old context sessions.hash
194 static void ctxStoreCleanUp (time_t now)
196 struct AFB_clientCtx *ctx;
199 // Loop on Sessions Table and remove anything that is older than timeout
200 for (idx=0; idx < sessions.max; idx++) {
201 ctx = sessions.store[idx];
202 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
203 ctxClientClose (ctx);
208 // This function will return exiting client context or newly created client context
209 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
211 struct AFB_clientCtx *clientCtx;
216 ctxStoreCleanUp (now);
218 /* search for an existing one not too old */
220 if (strlen(uuid) >= sizeof clientCtx->uuid) {
224 clientCtx = ctxStoreSearch(uuid);
225 if (clientCtx != NULL) {
231 /* returns a new one */
232 clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
233 if (clientCtx == NULL) {
237 clientCtx->values = (void*)(clientCtx + 1);
239 /* generate the uuid */
241 new_uuid(clientCtx->uuid);
243 strcpy(clientCtx->uuid, uuid);
247 strcpy(clientCtx->token, sessions.initok);
248 clientCtx->expiration = now + sessions.timeout;
249 if (!ctxStoreAdd (clientCtx)) {
256 clientCtx->access = now;
257 clientCtx->refcount++;
266 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
268 if (clientCtx != NULL)
269 clientCtx->refcount++;
273 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
275 if (clientCtx != NULL) {
276 assert(clientCtx->refcount != 0);
277 --clientCtx->refcount;
278 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
279 ctxStoreDel (clientCtx);
285 // Free Client Session Context
286 void ctxClientClose (struct AFB_clientCtx *clientCtx)
288 assert(clientCtx != NULL);
289 if (clientCtx->uuid[0] != 0) {
290 clientCtx->uuid[0] = 0;
291 ctxUuidFreeCB (clientCtx);
292 while(clientCtx->listeners != NULL)
293 ctxClientEventListenerRemove(clientCtx, clientCtx->listeners->listener);
294 if (clientCtx->refcount == 0) {
295 ctxStoreDel (clientCtx);
301 // Sample Generic Ping Debug API
302 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
304 assert(clientCtx != NULL);
305 assert(token != NULL);
307 // compare current token with previous one
308 if (!ctxIsActive (clientCtx, NOW))
311 if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
317 // generate a new token and update client context
318 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
320 assert(clientCtx != NULL);
322 // Old token was valid let's regenerate a new one
323 new_uuid(clientCtx->token);
325 // keep track of time for session timeout and further clean up
326 clientCtx->expiration = NOW + sessions.timeout;
329 static int add_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
331 struct afb_event_listener_list *iter, **prv;
337 iter = calloc(1, sizeof *iter);
342 iter->listener = listener;
347 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
355 int ctxClientEventListenerAdd(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
357 return add_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
360 static void remove_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
362 struct afb_event_listener_list *iter, **prv;
369 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
370 if (!--iter->refcount) {
380 void ctxClientEventListenerRemove(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
382 remove_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
385 static int send(struct afb_event_listener_list *head, const char *event, struct json_object *object)
387 struct afb_event_listener_list *iter;
392 while (iter != NULL) {
393 if (iter->listener.itf->expects == NULL || iter->listener.itf->expects(iter->listener.closure, event)) {
394 iter->listener.itf->send(iter->listener.closure, event, json_object_get(object));
403 int ctxClientEventSend(struct AFB_clientCtx *clientCtx, const char *event, struct json_object *object)
410 if (clientCtx != NULL) {
411 result = ctxIsActive(clientCtx, now) ? send(clientCtx->listeners, event, object) : 0;
413 result = send(sessions.listeners, event, object);
414 for (idx=0; idx < sessions.max; idx++) {
415 clientCtx = ctxClientAddRef(sessions.store[idx]);
416 if (clientCtx != NULL && ctxIsActive(clientCtx, now)) {
417 clientCtx = ctxClientAddRef(clientCtx);
418 result += send(clientCtx->listeners, event, object);
420 ctxClientUnref(clientCtx);
426 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
428 assert(clientCtx != NULL);
429 return clientCtx->uuid;
432 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
434 assert(clientCtx != NULL);
435 return clientCtx->token;
438 unsigned ctxClientGetLOA (struct AFB_clientCtx *clientCtx)
440 assert(clientCtx != NULL);
441 return clientCtx->loa;
444 void ctxClientSetLOA (struct AFB_clientCtx *clientCtx, unsigned loa)
446 assert(clientCtx != NULL);
447 clientCtx->loa = loa;
450 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
452 assert(clientCtx != NULL);
454 assert(index < sessions.apicount);
455 return clientCtx->values[index].value;
458 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
460 struct client_value prev;
461 assert(clientCtx != NULL);
463 assert(index < sessions.apicount);
464 prev = clientCtx->values[index];
465 clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
466 if (prev.value != NULL && prev.free_value != NULL)
467 prev.free_value(prev.value);