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.
26 #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))
44 /* structure for a cookie added to sessions */
47 struct cookie *next; /* link to next cookie */
48 const void *key; /* pointer key */
49 void *value; /* value */
50 void (*freecb)(void*); /* function to call when session is closed */
54 * structure for session
58 struct afb_session *next; /* link to the next */
59 unsigned refcount; /* external reference count of the session */
60 int timeout; /* timeout of the session */
61 time_t expiration; /* expiration time of the token */
62 pthread_mutex_t mutex; /* mutex of the session */
63 struct cookie *cookies[COOKIECOUNT]; /* cookies of the session */
64 uint8_t closed: 1; /* is the session closed ? */
65 uint8_t autoclose: 1; /* close the session when unreferenced */
66 uint8_t notinset: 1; /* session removed from the set of sessions */
67 char uuid[SIZEUUID]; /* long term authentication of remote client */
68 char token[SIZEUUID]; /* short term authentication of remote client */
71 /* Session UUID are store in a simple array [for 10 sessions this should be enough] */
73 int count; /* current number of sessions */
74 int max; /* maximum count of sessions */
75 int timeout; /* common initial timeout */
76 struct afb_session *heads[HEADCOUNT]; /* sessions */
77 char initok[SIZEUUID]; /* common initial token */
78 pthread_mutex_t mutex; /* declare a mutex to protect hash table */
85 .mutex = PTHREAD_MUTEX_INITIALIZER
88 /* generate a new fresh 'uuid' */
89 static void new_uuid(char uuid[SIZEUUID])
92 uuid_generate(newuuid);
93 uuid_unparse_lower(newuuid, uuid);
97 * Returns a tiny hash value for the 'text'.
99 * Tiny hash function inspired from pearson
101 static uint8_t pearson4(const char *text)
103 static uint8_t T[16] = {
104 4, 1, 6, 0, 9, 14, 11, 5,
105 2, 3, 12, 15, 10, 7, 8, 13
109 for (r = 0; (c = (uint8_t)*text) ; text++) {
113 return r; // % HEADCOUNT;
116 /* lock the set of sessions for exclusive access */
117 static inline void sessionset_lock()
119 pthread_mutex_lock(&sessions.mutex);
122 /* unlock the set of sessions of exclusive access */
123 static inline void sessionset_unlock()
125 pthread_mutex_unlock(&sessions.mutex);
129 * search within the set of sessions the session of 'uuid'.
130 * 'hashidx' is the precomputed hash for 'uuid'
131 * return the session or NULL
133 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
135 struct afb_session *session;
137 session = sessions.heads[hashidx];
138 while (session && strcmp(uuid, session->uuid))
139 session = session->next;
144 /* add 'session' to the set of sessions */
145 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
147 /* check availability */
148 if (sessions.count >= sessions.max) {
153 /* add the session */
154 session->next = sessions.heads[hashidx];
155 sessions.heads[hashidx] = session;
160 /* make a new uuid not used in the set of sessions */
161 static uint8_t sessionset_make_uuid (char uuid[SIZEUUID])
167 hashidx = pearson4(uuid);
168 } while(sessionset_search(uuid, hashidx));
172 /* lock the 'session' for exclusive access */
173 static inline void session_lock(struct afb_session *session)
175 pthread_mutex_lock(&session->mutex);
178 /* unlock the 'session' of exclusive access */
179 static inline void session_unlock(struct afb_session *session)
181 pthread_mutex_unlock(&session->mutex);
184 /* close the 'session' */
185 static void session_close(struct afb_session *session)
188 struct cookie *cookie;
190 /* close only one time */
191 if (!session->closed) {
195 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
196 while ((cookie = session->cookies[idx])) {
197 session->cookies[idx] = cookie->next;
198 if (cookie->freecb != NULL)
199 cookie->freecb(cookie->value);
206 /* destroy the 'session' */
207 static void session_destroy (struct afb_session *session)
209 pthread_mutex_destroy(&session->mutex);
213 /* update expiration of 'session' according to 'now' */
214 static void session_update_expiration(struct afb_session *session, time_t now)
219 /* compute expiration */
220 timeout = session->timeout;
221 if (timeout == AFB_SESSION_TIMEOUT_INFINITE)
222 expiration = MAX_EXPIRATION;
224 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
225 expiration = now + sessions.timeout;
227 expiration = now + timeout;
229 expiration = MAX_EXPIRATION;
232 /* record the expiration */
233 session->expiration = expiration;
237 * Add a new session with the 'uuid' (of 'hashidx')
238 * and the 'timeout' starting from 'now'.
239 * Add it to the set of sessions
240 * Return the created session
242 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
244 struct afb_session *session;
246 /* check arguments */
247 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
248 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
253 /* allocates a new one */
254 session = calloc(1, sizeof *session);
255 if (session == NULL) {
261 pthread_mutex_init(&session->mutex, NULL);
262 session->refcount = 1;
263 strcpy(session->uuid, uuid);
264 strcpy(session->token, sessions.initok);
265 session->timeout = timeout;
266 session_update_expiration(session, now);
269 if (sessionset_add(session, hashidx)) {
277 /* Remove expired sessions and return current time (now) */
278 static time_t sessionset_cleanup (int force)
280 struct afb_session *session, **prv;
284 /* Loop on Sessions Table and remove anything that is older than timeout */
286 for (idx = 0 ; idx < HEADCOUNT; idx++) {
287 prv = &sessions.heads[idx];
288 while ((session = *prv)) {
289 session_lock(session);
290 if (force || session->expiration < now)
291 session_close(session);
292 if (!session->closed)
293 prv = &session->next;
295 *prv = session->next;
297 session->notinset = 1;
298 if ( !session->refcount) {
299 session_destroy(session);
303 session_unlock(session);
310 * Initialize the session manager with a 'max_session_count',
311 * an initial common 'timeout' and an initial common token 'initok'.
313 * @param max_session_count maximum allowed session count in the same time
314 * @param timeout the initial default timeout of sessions
315 * @param initok the initial default token of sessions
318 int afb_session_init (int max_session_count, int timeout, const char *initok)
320 /* check parameters */
321 if (initok && strlen(initok) >= sizeof sessions.initok) {
322 ERROR("initial token '%s' too long (max length %d)",
323 initok, ((int)(sizeof sessions.initok)) - 1);
328 /* init the sessionset (after cleanup) */
330 sessionset_cleanup(1);
331 sessions.max = max_session_count;
332 sessions.timeout = timeout;
334 new_uuid(sessions.initok);
336 strcpy(sessions.initok, initok);
342 * Cleanup the sessionset of its closed or expired sessions
344 void afb_session_purge()
347 sessionset_cleanup(0);
352 * @return the initial token set at initialization
354 const char *afb_session_initial_token()
356 return sessions.initok;
359 /* Searchs the session of 'uuid' */
360 struct afb_session *afb_session_search (const char *uuid)
362 struct afb_session *session;
365 sessionset_cleanup(0);
366 session = sessionset_search(uuid, pearson4(uuid));
367 session = afb_session_addref(session);
374 * Creates a new session with 'timeout'
376 struct afb_session *afb_session_create (int timeout)
378 return afb_session_get(NULL, timeout, NULL);
381 /* This function will return exiting session or newly created session */
382 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
384 char _uuid_[SIZEUUID];
386 struct afb_session *session;
392 now = sessionset_cleanup(0);
394 /* search for an existing one not too old */
396 hashidx = sessionset_make_uuid(_uuid_);
399 hashidx = pearson4(uuid);
400 session = sessionset_search(uuid, hashidx);
403 afb_session_addref(session);
408 /* create the session */
409 session = session_add(uuid, timeout, now, hashidx);
419 /* increase the use count on 'session' (can be NULL) */
420 struct afb_session *afb_session_addref(struct afb_session *session)
423 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
427 /* decrease the use count of 'session' (can be NULL) */
428 void afb_session_unref(struct afb_session *session)
433 session_lock(session);
434 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
435 if (session->autoclose)
436 session_close(session);
437 if (session->notinset) {
438 session_destroy(session);
442 session_unlock(session);
445 /* close 'session' */
446 void afb_session_close (struct afb_session *session)
448 session_lock(session);
449 session_close(session);
450 session_unlock(session);
454 * Set the 'autoclose' flag of the 'session'
456 * A session whose autoclose flag is true will close as
457 * soon as it is no more referenced.
459 * @param session the session to set
460 * @param autoclose the value to set
462 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
464 session->autoclose = !!autoclose;
467 /* is 'session' closed? */
468 int afb_session_is_closed (struct afb_session *session)
470 return session->closed;
474 * check whether the token of 'session' is 'token'
475 * return 1 if true or 0 otherwise
477 int afb_session_check_token (struct afb_session *session, const char *token)
481 session_unlock(session);
483 && session->expiration >= NOW
484 && !(session->token[0] && strcmp (token, session->token));
485 session_unlock(session);
489 /* generate a new token and update client context */
490 void afb_session_new_token (struct afb_session *session)
492 /* Old token was valid let's regenerate a new one */
493 new_uuid(session->token);
495 /* keep track of time for session timeout and further clean up */
496 session_update_expiration(session, NOW);
499 /* Returns the uuid of 'session' */
500 const char *afb_session_uuid (struct afb_session *session)
502 return session->uuid;
505 /* Returns the token of 'session' */
506 const char *afb_session_token (struct afb_session *session)
508 return session->token;
512 * Get the index of the 'key' in the cookies array.
513 * @param key the key to scan
514 * @return the index of the list for key within cookies
516 static int cookeyidx(const void *key)
518 intptr_t x = (intptr_t)key;
519 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
520 return r & COOKIEMASK;
524 * Set, get, replace, remove a cookie of 'key' for the 'session'
526 * The behaviour of this function depends on its parameters:
528 * @param session the session
529 * @param key the key of the cookie
530 * @param makecb the creation function or NULL
531 * @param freecb the release function or NULL
532 * @param closure an argument for makecb or the value if makecb==NULL
533 * @param replace a boolean enforcing replecement of the previous value
535 * @return the value of the cookie
537 * The 'key' is a pointer and compared as pointers.
539 * For getting the current value of the cookie:
541 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
543 * For storing the value of the cookie
545 * afb_session_cookie(session, key, NULL, NULL, value, 1)
547 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
551 struct cookie *cookie, **prv;
553 /* get key hashed index */
554 idx = cookeyidx(key);
556 /* lock session and search for the cookie of 'key' */
557 session_lock(session);
558 prv = &session->cookies[idx];
562 /* 'key' not found, create value using 'closure' and 'makecb' */
563 value = makecb ? makecb(closure) : closure;
564 /* store the the only if it has some meaning */
565 if (replace || makecb || freecb) {
566 cookie = malloc(sizeof *cookie);
569 /* calling freecb if there is no makecb may have issue */
570 if (makecb && freecb)
575 cookie->value = value;
576 cookie->freecb = freecb;
582 } else if (cookie->key == key) {
583 /* cookie of key found */
585 /* not replacing, get the value */
586 value = cookie->value;
588 /* create value using 'closure' and 'makecb' */
589 value = makecb ? makecb(closure) : closure;
591 /* free previous value is needed */
592 if (cookie->value != value && cookie->freecb)
593 cookie->freecb(cookie->value);
595 /* if both value and freecb are NULL drop the cookie */
596 if (!value && !freecb) {
600 /* store the value and its releaser */
601 cookie->value = value;
602 cookie->freecb = freecb;
607 prv = &(cookie->next);
611 /* unlock the session and return the value */
612 session_unlock(session);
617 * Get the cookie of 'key' in the 'session'.
619 * @param session the session to search in
620 * @param key the key of the data to retrieve
622 * @return the data staored for the key or NULL if the key isn't found
624 void *afb_session_get_cookie(struct afb_session *session, const void *key)
626 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
630 * Set the cookie of 'key' in the 'session' to the 'value' that can be
631 * cleaned using 'freecb' (if not null).
633 * @param session the session to set
634 * @param key the key of the data to store
635 * @param value the value to store at key
636 * @param freecb a function to use when the cookie value is to remove (or null)
638 * @return the data staored for the key or NULL if the key isn't found
641 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
643 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));