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"
37 #define COOKIEMASK (COOKIECOUNT - 1)
39 #define _MAXEXP_ ((time_t)(~(time_t)0))
40 #define _MAXEXP2_ ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
41 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
42 #define NOW (time(NULL))
49 void (*freecb)(void*);
54 struct afb_session *next; /* link to the next */
57 time_t expiration; // expiration time of the token
58 pthread_mutex_t mutex;
59 struct cookie *cookies[COOKIECOUNT];
62 char uuid[SIZEUUID]; // long term authentication of remote client
63 char token[SIZEUUID]; // short term authentication of remote client
66 // Session UUID are store in a simple array [for 10 sessions this should be enough]
68 pthread_mutex_t mutex; // declare a mutex to protect hash table
69 struct afb_session *heads[HEADCOUNT]; // sessions
70 int count; // current number of sessions
73 char initok[SIZEUUID];
77 static void new_uuid(char uuid[SIZEUUID])
80 uuid_generate(newuuid);
81 uuid_unparse_lower(newuuid, uuid);
84 static inline void lock(struct afb_session *session)
86 pthread_mutex_lock(&session->mutex);
89 static inline void unlock(struct afb_session *session)
91 pthread_mutex_unlock(&session->mutex);
94 // Free context [XXXX Should be protected again memory abort XXXX]
95 static void close_session(struct afb_session *session)
98 struct cookie *cookie;
101 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
102 while ((cookie = session->cookies[idx])) {
103 session->cookies[idx] = cookie->next;
104 if (cookie->freecb != NULL)
105 cookie->freecb(cookie->value);
111 /* tiny hash function inspired from pearson */
112 static int pearson4(const char *text)
114 static uint8_t T[16] = {
115 4, 1, 6, 0, 9, 14, 11, 5,
116 2, 3, 12, 15, 10, 7, 8, 13
120 for (r = 0; (c = (uint8_t)*text) ; text++) {
124 return r; // % HEADCOUNT;
128 * Initialize the session manager with a 'max_session_count',
129 * an initial common 'timeout' and an initial common token 'initok'.
131 * @param max_session_count maximum allowed session count in the same time
132 * @param timeout the initial default timeout of sessions
133 * @param initok the initial default token of sessions
136 int afb_session_init (int max_session_count, int timeout, const char *initok)
138 pthread_mutex_init(&sessions.mutex, NULL);
139 sessions.max = max_session_count;
140 sessions.timeout = timeout;
142 /* without token, a secret is made to forbid creation of sessions */
143 new_uuid(sessions.initok);
144 else if (strlen(initok) < sizeof sessions.initok)
145 strcpy(sessions.initok, initok);
147 ERROR("initial token '%s' too long (max length %d)", initok, ((int)(sizeof sessions.initok)) - 1);
154 const char *afb_session_initial_token()
156 return sessions.initok;
159 static struct afb_session *search (const char* uuid, int idx)
161 struct afb_session *session;
163 session = sessions.heads[idx];
164 while (session && strcmp(uuid, session->uuid))
165 session = session->next;
170 static void destroy (struct afb_session *session)
172 struct afb_session **prv;
174 assert (session != NULL);
176 close_session(session);
177 pthread_mutex_lock(&sessions.mutex);
178 prv = &sessions.heads[(int)session->idx];
181 prv = &((*prv)->next);
183 *prv = session->next;
185 pthread_mutex_destroy(&session->mutex);
189 pthread_mutex_unlock(&sessions.mutex);
192 // Loop on every entry and remove old context sessions.hash
193 static time_t cleanup ()
195 struct afb_session *session, *next;
199 // Loop on Sessions Table and remove anything that is older than timeout
201 for (idx = 0 ; idx < HEADCOUNT; idx++) {
202 session = sessions.heads[idx];
204 next = session->next;
205 if (session->expiration < now)
206 afb_session_close(session);
213 static void update_timeout(struct afb_session *session, time_t now, int timeout)
217 /* compute expiration */
218 if (timeout == AFB_SESSION_TIMEOUT_INFINITE)
219 expiration = MAX_EXPIRATION;
221 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
222 expiration = now + sessions.timeout;
224 expiration = now + timeout;
226 expiration = MAX_EXPIRATION;
229 /* record the values */
230 session->timeout = timeout;
231 session->expiration = expiration;
234 static void update_expiration(struct afb_session *session, time_t now)
236 update_timeout(session, now, session->timeout);
239 static struct afb_session *add_session (const char *uuid, int timeout, time_t now, int idx)
241 struct afb_session *session;
243 /* check arguments */
244 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
245 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
250 /* check session count */
251 if (sessions.count >= sessions.max) {
256 /* allocates a new one */
257 session = calloc(1, sizeof *session);
258 if (session == NULL) {
264 pthread_mutex_init(&session->mutex, NULL);
265 session->refcount = 1;
266 strcpy(session->uuid, uuid);
267 strcpy(session->token, sessions.initok);
268 update_timeout(session, now, timeout);
271 session->idx = (char)idx;
272 session->next = sessions.heads[idx];
273 sessions.heads[idx] = session;
279 /* create a new session for the given timeout */
280 static struct afb_session *new_session (int timeout, time_t now)
287 idx = pearson4(uuid);
288 } while(search(uuid, idx));
289 return add_session(uuid, timeout, now, idx);
292 /* Creates a new session with 'timeout' */
293 struct afb_session *afb_session_create (int timeout)
296 struct afb_session *session;
299 pthread_mutex_lock(&sessions.mutex);
301 session = new_session(timeout, now);
302 pthread_mutex_unlock(&sessions.mutex);
307 /* Searchs the session of 'uuid' */
308 struct afb_session *afb_session_search (const char *uuid)
310 struct afb_session *session;
313 pthread_mutex_lock(&sessions.mutex);
315 session = search(uuid, pearson4(uuid));
317 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
318 pthread_mutex_unlock(&sessions.mutex);
323 /* This function will return exiting session or newly created session */
324 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
327 struct afb_session *session;
331 pthread_mutex_lock(&sessions.mutex);
334 /* search for an existing one not too old */
336 session = new_session(timeout, now);
338 idx = pearson4(uuid);
339 session = search(uuid, idx);
341 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
342 pthread_mutex_unlock(&sessions.mutex);
347 session = add_session (uuid, timeout, now, idx);
349 pthread_mutex_unlock(&sessions.mutex);
352 *created = !!session;
357 /* increase the use count on the session */
358 struct afb_session *afb_session_addref(struct afb_session *session)
361 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
365 /* decrease the use count of the session */
366 void afb_session_unref(struct afb_session *session)
368 if (session != NULL) {
369 assert(session->refcount != 0);
370 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
371 pthread_mutex_lock(&session->mutex);
372 if (session->autoclose || session->uuid[0] == 0)
375 pthread_mutex_unlock(&session->mutex);
380 // close Client Session Context
381 void afb_session_close (struct afb_session *session)
383 assert(session != NULL);
384 pthread_mutex_lock(&session->mutex);
385 if (session->uuid[0] != 0) {
386 session->uuid[0] = 0;
387 if (session->refcount)
388 close_session(session);
394 pthread_mutex_unlock(&session->mutex);
397 /* set the autoclose flag */
398 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
400 assert(session != NULL);
401 session->autoclose = (char)!!autoclose;
404 // is the session active?
405 int afb_session_is_active (struct afb_session *session)
407 assert(session != NULL);
408 return !!session->uuid[0];
411 // is the session closed?
412 int afb_session_is_closed (struct afb_session *session)
414 assert(session != NULL);
415 return !session->uuid[0];
418 // Sample Generic Ping Debug API
419 int afb_session_check_token (struct afb_session *session, const char *token)
421 assert(session != NULL);
422 assert(token != NULL);
424 if (!session->uuid[0])
427 if (session->expiration < NOW)
430 if (session->token[0] && strcmp (token, session->token) != 0)
436 // generate a new token and update client context
437 void afb_session_new_token (struct afb_session *session)
439 assert(session != NULL);
441 // Old token was valid let's regenerate a new one
442 new_uuid(session->token);
444 // keep track of time for session timeout and further clean up
445 update_expiration(session, NOW);
448 /* Returns the uuid of 'session' */
449 const char *afb_session_uuid (struct afb_session *session)
451 assert(session != NULL);
452 return session->uuid;
455 /* Returns the token of 'session' */
456 const char *afb_session_token (struct afb_session *session)
458 assert(session != NULL);
459 return session->token;
463 * Get the index of the 'key' in the cookies array.
464 * @param key the key to scan
465 * @return the index of the list for key within cookies
467 static int cookeyidx(const void *key)
469 intptr_t x = (intptr_t)key;
470 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
471 return r & COOKIEMASK;
475 * Set, get, replace, remove a cookie of 'key' for the 'session'
477 * The behaviour of this function depends on its parameters:
479 * @param session the session
480 * @param key the key of the cookie
481 * @param makecb the creation function or NULL
482 * @param freecb the release function or NULL
483 * @param closure an argument for makecb or the value if makecb==NULL
484 * @param replace a boolean enforcing replecement of the previous value
486 * @return the value of the cookie
488 * The 'key' is a pointer and compared as pointers.
490 * For getting the current value of the cookie:
492 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
494 * For storing the value of the cookie
496 * afb_session_cookie(session, key, NULL, NULL, value, 1)
498 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
502 struct cookie *cookie, **prv;
504 /* get key hashed index */
505 idx = cookeyidx(key);
507 /* lock session and search for the cookie of 'key' */
509 prv = &session->cookies[idx];
513 /* 'key' not found, create value using 'closure' and 'makecb' */
514 value = makecb ? makecb(closure) : closure;
515 /* store the the only if it has some meaning */
516 if (replace || makecb || freecb) {
517 cookie = malloc(sizeof *cookie);
520 /* calling freecb if there is no makecb may have issue */
521 if (makecb && freecb)
526 cookie->value = value;
527 cookie->freecb = freecb;
533 } else if (cookie->key == key) {
534 /* cookie of key found */
536 /* not replacing, get the value */
537 value = cookie->value;
539 /* create value using 'closure' and 'makecb' */
540 value = makecb ? makecb(closure) : closure;
542 /* free previous value is needed */
543 if (cookie->value != value && cookie->freecb)
544 cookie->freecb(cookie->value);
546 /* store the value and its releaser */
547 cookie->value = value;
548 cookie->freecb = freecb;
550 /* but if both are NULL drop the cookie */
551 if (!value && !freecb) {
558 prv = &(cookie->next);
562 /* unlock the session and return the value */
567 void *afb_session_get_cookie(struct afb_session *session, const void *key)
569 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
572 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
574 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));