2 * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
3 * Author "Fulup Ar Foll"
4 * Author: José Bollo <jose.bollo@iot.bzh>
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
25 #include <uuid/uuid.h>
29 #include <json-c/json.h>
31 #include "afb-session.h"
36 #define COOKEYMASK (COOKEYCOUNT - 1)
38 #define NOW (time(NULL))
45 void (*freecb)(void*);
50 struct afb_session *next; /* link to the next */
53 time_t expiration; // expiration time of the token
54 pthread_mutex_t mutex;
56 char uuid[37]; // long term authentication of remote client
57 char token[37]; // short term authentication of remote client
58 struct cookie *cookies[COOKEYCOUNT];
61 // Session UUID are store in a simple array [for 10 sessions this should be enough]
63 pthread_mutex_t mutex; // declare a mutex to protect hash table
64 struct afb_session *heads[HEADCOUNT]; // sessions
65 int count; // current number of sessions
72 static void new_uuid(char uuid[37])
75 uuid_generate(newuuid);
76 uuid_unparse_lower(newuuid, uuid);
79 static inline void lock(struct afb_session *session)
81 pthread_mutex_lock(&session->mutex);
84 static inline void unlock(struct afb_session *session)
86 pthread_mutex_unlock(&session->mutex);
89 // Free context [XXXX Should be protected again memory abort XXXX]
90 static void remove_all_cookies(struct afb_session *session)
93 struct cookie *cookie, *next;
96 for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
97 cookie = session->cookies[idx];
98 session->cookies[idx] = NULL;
99 while (cookie != NULL) {
101 if (cookie->freecb != NULL)
102 cookie->freecb(cookie->value);
109 /* tiny hash function inspired from pearson */
110 static int pearson4(const char *text)
112 static uint8_t T[16] = {
113 4, 1, 6, 0, 9, 14, 11, 5,
114 2, 3, 12, 15, 10, 7, 8, 13
118 for (r = 0; (c = (uint8_t)*text) ; text++) {
122 return r; // % HEADCOUNT;
125 // Create a new store in RAM, not that is too small it will be automatically extended
126 void afb_session_init (int max_session_count, int timeout, const char *initok)
128 pthread_mutex_init(&sessions.mutex, NULL);
129 sessions.max = max_session_count;
130 sessions.timeout = timeout;
132 /* without token, a secret is made to forbid creation of sessions */
133 new_uuid(sessions.initok);
134 else if (strlen(initok) < sizeof sessions.initok)
135 strcpy(sessions.initok, initok);
137 ERROR("initial token '%s' too long (max length %d)", initok, ((int)(sizeof sessions.initok)) - 1);
142 const char *afb_session_initial_token()
144 return sessions.initok;
147 static struct afb_session *search (const char* uuid, int idx)
149 struct afb_session *session;
151 session = sessions.heads[idx];
152 while (session && strcmp(uuid, session->uuid))
153 session = session->next;
158 static void destroy (struct afb_session *session)
160 struct afb_session **prv;
162 assert (session != NULL);
164 remove_all_cookies(session);
165 pthread_mutex_lock(&sessions.mutex);
166 prv = &sessions.heads[(int)session->idx];
169 prv = &((*prv)->next);
171 *prv = session->next;
173 pthread_mutex_destroy(&session->mutex);
177 pthread_mutex_unlock(&sessions.mutex);
180 // Check if context timeout or not
181 static int is_expired (struct afb_session *ctx, time_t now)
183 assert (ctx != NULL);
184 return ctx->expiration < now;
187 // Check if context is active or not
188 static int is_active (struct afb_session *ctx, time_t now)
190 assert (ctx != NULL);
191 return ctx->uuid[0] != 0 && ctx->expiration >= now;
194 // Loop on every entry and remove old context sessions.hash
195 static time_t cleanup ()
197 struct afb_session *session, *next;
201 // Loop on Sessions Table and remove anything that is older than timeout
203 for (idx = 0 ; idx < HEADCOUNT; idx++) {
204 session = sessions.heads[idx];
206 next = session->next;
207 if (is_expired(session, now))
208 afb_session_close(session);
215 static struct afb_session *add_session (const char *uuid, int timeout, time_t now, int idx)
217 struct afb_session *session;
219 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
220 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
225 if (sessions.count >= sessions.max) {
230 /* allocates a new one */
231 session = calloc(1, sizeof *session);
232 if (session == NULL) {
237 pthread_mutex_init(&session->mutex, NULL);
238 session->refcount = 1;
239 strcpy(session->uuid, uuid);
240 strcpy(session->token, sessions.initok);
243 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
244 timeout = sessions.timeout;
245 session->timeout = timeout;
246 session->expiration = now + timeout;
247 if (timeout == AFB_SESSION_TIMEOUT_INFINITE || session->expiration < 0) {
248 session->expiration = (time_t)(~(time_t)0);
249 if (session->expiration < 0)
250 session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1);
253 session->idx = (char)idx;
254 session->next = sessions.heads[idx];
255 sessions.heads[idx] = session;
261 static struct afb_session *new_session (int timeout, time_t now)
268 idx = pearson4(uuid);
269 } while(search(uuid, idx));
270 return add_session(uuid, timeout, now, idx);
273 /* Creates a new session with 'timeout' */
274 struct afb_session *afb_session_create (int timeout)
277 struct afb_session *session;
280 pthread_mutex_lock(&sessions.mutex);
282 session = new_session(timeout, now);
283 pthread_mutex_unlock(&sessions.mutex);
288 /* Searchs the session of 'uuid' */
289 struct afb_session *afb_session_search (const char *uuid)
291 struct afb_session *session;
294 pthread_mutex_lock(&sessions.mutex);
296 session = search(uuid, pearson4(uuid));
298 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
299 pthread_mutex_unlock(&sessions.mutex);
304 /* This function will return exiting session or newly created session */
305 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
308 struct afb_session *session;
312 pthread_mutex_lock(&sessions.mutex);
315 /* search for an existing one not too old */
317 session = new_session(timeout, now);
319 idx = pearson4(uuid);
320 session = search(uuid, idx);
322 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
323 pthread_mutex_unlock(&sessions.mutex);
328 session = add_session (uuid, timeout, now, idx);
330 pthread_mutex_unlock(&sessions.mutex);
333 *created = !!session;
338 /* increase the use count on the session */
339 struct afb_session *afb_session_addref(struct afb_session *session)
342 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
346 /* decrease the use count of the session */
347 void afb_session_unref(struct afb_session *session)
349 if (session != NULL) {
350 assert(session->refcount != 0);
351 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
352 pthread_mutex_lock(&session->mutex);
353 if (session->uuid[0] == 0)
356 pthread_mutex_unlock(&session->mutex);
361 // Free Client Session Context
362 void afb_session_close (struct afb_session *session)
364 assert(session != NULL);
365 pthread_mutex_lock(&session->mutex);
366 if (session->uuid[0] != 0) {
367 session->uuid[0] = 0;
368 remove_all_cookies(session);
369 if (session->refcount == 0) {
374 pthread_mutex_unlock(&session->mutex);
377 // Sample Generic Ping Debug API
378 int afb_session_check_token (struct afb_session *session, const char *token)
380 assert(session != NULL);
381 assert(token != NULL);
383 // compare current token with previous one
384 if (!is_active (session, NOW))
387 if (session->token[0] && strcmp (token, session->token) != 0)
393 // generate a new token and update client context
394 void afb_session_new_token (struct afb_session *session)
396 assert(session != NULL);
398 // Old token was valid let's regenerate a new one
399 new_uuid(session->token);
401 // keep track of time for session timeout and further clean up
402 if (session->timeout != 0)
403 session->expiration = NOW + session->timeout;
406 /* Returns the uuid of 'session' */
407 const char *afb_session_uuid (struct afb_session *session)
409 assert(session != NULL);
410 return session->uuid;
413 /* Returns the token of 'session' */
414 const char *afb_session_token (struct afb_session *session)
416 assert(session != NULL);
417 return session->token;
421 * Get the index of the 'key' in the cookies array.
422 * @param key the key to scan
423 * @return the index of the list for key within cookies
425 static int cookeyidx(const void *key)
427 intptr_t x = (intptr_t)key;
428 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
429 return r & COOKEYMASK;
433 * Set, get, replace, remove a cookie of 'key' for the 'session'
435 * The behaviour of this function depends on its parameters:
437 * @param session the session
438 * @param key the key of the cookie
439 * @param makecb the creation function
440 * @param freecb the release function
441 * @param closure an argument
442 * @param replace a boolean enforcing replecement of the previous value
444 * @return the value of the cookie
446 * The 'key' is a pointer and compared as pointers.
448 * For getting the current value of the cookie:
450 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
452 * For storing the value of the cookie
454 * afb_session_cookie(session, key, NULL, NULL, value, 1)
456 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
460 struct cookie *cookie, **prv;
462 /* get key hashed index */
463 idx = cookeyidx(key);
465 /* lock session and search for the cookie of 'key' */
467 prv = &session->cookies[idx];
471 /* 'key' not found, create value using 'closure' and 'makecb' */
472 value = makecb ? makecb(closure) : closure;
473 /* store the the only if it has some meaning */
474 if (replace || makecb || freecb) {
475 cookie = malloc(sizeof *cookie);
478 /* calling freecb if there is no makecb may have issue */
479 if (makecb && freecb)
484 cookie->value = value;
485 cookie->freecb = freecb;
491 } else if (cookie->key == key) {
492 /* cookie of key found */
494 /* not replacing, get the value */
495 value = cookie->value;
497 /* create value using 'closure' and 'makecb' */
498 value = makecb ? makecb(closure) : closure;
500 /* free previous value is needed */
501 if (cookie->value != value && cookie->freecb)
502 cookie->freecb(cookie->value);
504 /* store the value and its releaser */
505 cookie->value = value;
506 cookie->freecb = freecb;
508 /* but if both are NULL drop the cookie */
509 if (!value && !freecb) {
516 prv = &(cookie->next);
520 /* unlock the session and return the value */
525 void *afb_session_get_cookie(struct afb_session *session, const void *key)
527 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
530 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
532 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));