2 * Copyright (C) 2015-2018 "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.
29 #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_now())
45 * structure for a cookie added to sessions
49 struct cookie *next; /**< link to next cookie */
50 const void *key; /**< pointer key */
51 void *value; /**< value */
52 void (*freecb)(void*); /**< function to call when session is closed */
56 * structure for session
60 struct afb_session *next; /**< link to the next */
61 unsigned refcount; /**< count of reference to the session */
62 int timeout; /**< timeout of the session */
63 time_t expiration; /**< expiration time of the token */
64 pthread_mutex_t mutex; /**< mutex of the session */
65 struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
66 char *lang; /**< current language setting for the session */
67 uint8_t closed: 1; /**< is the session closed ? */
68 uint8_t autoclose: 1; /**< close the session when unreferenced */
69 uint8_t notinset: 1; /**< session removed from the set of sessions */
70 uuid_stringz_t uuid; /**< long term authentication of remote client */
71 uuid_stringz_t token; /**< short term authentication of remote client */
75 * structure for managing sessions
78 int count; /**< current number of sessions */
79 int max; /**< maximum count of sessions */
80 int timeout; /**< common initial timeout */
81 struct afb_session *heads[HEADCOUNT]; /**< sessions */
82 uuid_stringz_t initok; /**< common initial token */
83 pthread_mutex_t mutex; /**< declare a mutex to protect hash table */
90 .mutex = PTHREAD_MUTEX_INITIALIZER
94 * Get the actual raw time
96 static inline time_t time_now()
99 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
103 /* lock the set of sessions for exclusive access */
104 static inline void sessionset_lock()
106 pthread_mutex_lock(&sessions.mutex);
109 /* unlock the set of sessions of exclusive access */
110 static inline void sessionset_unlock()
112 pthread_mutex_unlock(&sessions.mutex);
116 * search within the set of sessions the session of 'uuid'.
117 * 'hashidx' is the precomputed hash for 'uuid'
118 * return the session or NULL
120 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
122 struct afb_session *session;
124 session = sessions.heads[hashidx];
125 while (session && strcmp(uuid, session->uuid))
126 session = session->next;
131 /* add 'session' to the set of sessions */
132 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
134 /* check availability */
135 if (sessions.max && sessions.count >= sessions.max) {
140 /* add the session */
141 session->next = sessions.heads[hashidx];
142 sessions.heads[hashidx] = session;
147 /* make a new uuid not used in the set of sessions */
148 static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
153 uuid_new_stringz(uuid);
154 hashidx = pearson4(uuid);
155 } while(sessionset_search(uuid, hashidx));
159 /* lock the 'session' for exclusive access */
160 static inline void session_lock(struct afb_session *session)
162 pthread_mutex_lock(&session->mutex);
165 /* unlock the 'session' of exclusive access */
166 static inline void session_unlock(struct afb_session *session)
168 pthread_mutex_unlock(&session->mutex);
171 /* close the 'session' */
172 static void session_close(struct afb_session *session)
175 struct cookie *cookie;
177 /* close only one time */
178 if (!session->closed) {
183 afb_hook_session_close(session);
185 /* release cookies */
186 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
187 while ((cookie = session->cookies[idx])) {
188 session->cookies[idx] = cookie->next;
189 if (cookie->freecb != NULL)
190 cookie->freecb(cookie->value);
197 /* destroy the 'session' */
198 static void session_destroy (struct afb_session *session)
200 afb_hook_session_destroy(session);
201 pthread_mutex_destroy(&session->mutex);
206 /* update expiration of 'session' according to 'now' */
207 static void session_update_expiration(struct afb_session *session, time_t now)
211 /* compute expiration */
212 expiration = now + afb_session_timeout(session);
214 expiration = MAX_EXPIRATION;
216 /* record the expiration */
217 session->expiration = expiration;
221 * Add a new session with the 'uuid' (of 'hashidx')
222 * and the 'timeout' starting from 'now'.
223 * Add it to the set of sessions
224 * Return the created session
226 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
228 struct afb_session *session;
230 /* check arguments */
231 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
232 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
237 /* allocates a new one */
238 session = calloc(1, sizeof *session);
239 if (session == NULL) {
245 pthread_mutex_init(&session->mutex, NULL);
246 session->refcount = 1;
247 strcpy(session->uuid, uuid);
248 strcpy(session->token, sessions.initok);
249 session->timeout = timeout;
250 session_update_expiration(session, now);
253 if (sessionset_add(session, hashidx)) {
258 afb_hook_session_create(session);
263 /* Remove expired sessions and return current time (now) */
264 static time_t sessionset_cleanup (int force)
266 struct afb_session *session, **prv;
270 /* Loop on Sessions Table and remove anything that is older than timeout */
272 for (idx = 0 ; idx < HEADCOUNT; idx++) {
273 prv = &sessions.heads[idx];
274 while ((session = *prv)) {
275 session_lock(session);
276 if (force || session->expiration < now)
277 session_close(session);
278 if (!session->closed)
279 prv = &session->next;
281 *prv = session->next;
283 session->notinset = 1;
284 if ( !session->refcount) {
285 session_destroy(session);
289 session_unlock(session);
296 * Initialize the session manager with a 'max_session_count',
297 * an initial common 'timeout' and an initial common token 'initok'.
299 * @param max_session_count maximum allowed session count in the same time
300 * @param timeout the initial default timeout of sessions
301 * @param initok the initial default token of sessions
304 int afb_session_init (int max_session_count, int timeout, const char *initok)
306 /* check parameters */
307 if (initok && strlen(initok) >= sizeof sessions.initok) {
308 ERROR("initial token '%s' too long (max length %d)",
309 initok, ((int)(sizeof sessions.initok)) - 1);
314 /* init the sessionset (after cleanup) */
316 sessionset_cleanup(1);
317 sessions.max = max_session_count;
318 sessions.timeout = timeout;
320 uuid_new_stringz(sessions.initok);
322 strcpy(sessions.initok, initok);
328 * Iterate the sessions and call 'callback' with
329 * the 'closure' for each session.
331 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
333 struct afb_session *session;
336 /* Loop on Sessions Table and remove anything that is older than timeout */
338 for (idx = 0 ; idx < HEADCOUNT; idx++) {
339 session = sessions.heads[idx];
341 if (!session->closed)
342 callback(closure, session);
343 session = session->next;
350 * Cleanup the sessionset of its closed or expired sessions
352 void afb_session_purge()
355 sessionset_cleanup(0);
360 * @return the initial token set at initialization
362 const char *afb_session_initial_token()
364 return sessions.initok;
367 /* Searchs the session of 'uuid' */
368 struct afb_session *afb_session_search (const char *uuid)
370 struct afb_session *session;
373 sessionset_cleanup(0);
374 session = sessionset_search(uuid, pearson4(uuid));
375 session = afb_session_addref(session);
382 * Creates a new session with 'timeout'
384 struct afb_session *afb_session_create (int timeout)
386 return afb_session_get(NULL, timeout, NULL);
390 * Returns the timeout of 'session' in seconds
392 int afb_session_timeout(struct afb_session *session)
396 /* compute timeout */
397 timeout = session->timeout;
398 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
399 timeout = sessions.timeout;
406 * Returns the second remaining before expiration of 'session'
408 int afb_session_what_remains(struct afb_session *session)
410 return (int)(session->expiration - NOW);
413 /* This function will return exiting session or newly created session */
414 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
416 uuid_stringz_t _uuid_;
418 struct afb_session *session;
424 now = sessionset_cleanup(0);
426 /* search for an existing one not too old */
428 hashidx = sessionset_make_uuid(_uuid_);
431 hashidx = pearson4(uuid);
432 session = sessionset_search(uuid, hashidx);
435 afb_session_addref(session);
440 /* create the session */
441 session = session_add(uuid, timeout, now, hashidx);
451 /* increase the use count on 'session' (can be NULL) */
452 struct afb_session *afb_session_addref(struct afb_session *session)
454 if (session != NULL) {
455 afb_hook_session_addref(session);
456 session_lock(session);
458 session_unlock(session);
463 /* decrease the use count of 'session' (can be NULL) */
464 void afb_session_unref(struct afb_session *session)
469 afb_hook_session_unref(session);
470 session_lock(session);
471 if (!--session->refcount) {
472 if (session->autoclose)
473 session_close(session);
474 if (session->notinset) {
475 session_destroy(session);
479 session_unlock(session);
482 /* close 'session' */
483 void afb_session_close (struct afb_session *session)
485 session_lock(session);
486 session_close(session);
487 session_unlock(session);
491 * Set the 'autoclose' flag of the 'session'
493 * A session whose autoclose flag is true will close as
494 * soon as it is no more referenced.
496 * @param session the session to set
497 * @param autoclose the value to set
499 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
501 session->autoclose = !!autoclose;
504 /* is 'session' closed? */
505 int afb_session_is_closed (struct afb_session *session)
507 return session->closed;
511 * check whether the token of 'session' is 'token'
512 * return 1 if true or 0 otherwise
514 int afb_session_check_token (struct afb_session *session, const char *token)
518 session_lock(session);
520 && session->expiration >= NOW
521 && !(session->token[0] && strcmp (token, session->token));
522 session_unlock(session);
526 /* generate a new token and update client context */
527 void afb_session_new_token (struct afb_session *session)
529 session_lock(session);
530 uuid_new_stringz(session->token);
531 session_update_expiration(session, NOW);
532 afb_hook_session_renew(session);
533 session_unlock(session);
536 /* Returns the uuid of 'session' */
537 const char *afb_session_uuid (struct afb_session *session)
539 return session->uuid;
542 /* Returns the token of 'session' */
543 const char *afb_session_token (struct afb_session *session)
545 return session->token;
549 * Get the index of the 'key' in the cookies array.
550 * @param key the key to scan
551 * @return the index of the list for key within cookies
553 static int cookeyidx(const void *key)
555 intptr_t x = (intptr_t)key;
556 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
557 return r & COOKIEMASK;
561 * Set, get, replace, remove a cookie of 'key' for the 'session'
563 * The behaviour of this function depends on its parameters:
565 * @param session the session
566 * @param key the key of the cookie
567 * @param makecb the creation function or NULL
568 * @param freecb the release function or NULL
569 * @param closure an argument for makecb or the value if makecb==NULL
570 * @param replace a boolean enforcing replecement of the previous value
572 * @return the value of the cookie
574 * The 'key' is a pointer and compared as pointers.
576 * For getting the current value of the cookie:
578 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
580 * For storing the value of the cookie
582 * afb_session_cookie(session, key, NULL, NULL, value, 1)
584 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
588 struct cookie *cookie, **prv;
590 /* get key hashed index */
591 idx = cookeyidx(key);
593 /* lock session and search for the cookie of 'key' */
594 session_lock(session);
595 prv = &session->cookies[idx];
599 /* 'key' not found, create value using 'closure' and 'makecb' */
600 value = makecb ? makecb(closure) : closure;
601 /* store the the only if it has some meaning */
602 if (replace || makecb || freecb) {
603 cookie = malloc(sizeof *cookie);
606 /* calling freecb if there is no makecb may have issue */
607 if (makecb && freecb)
612 cookie->value = value;
613 cookie->freecb = freecb;
619 } else if (cookie->key == key) {
620 /* cookie of key found */
622 /* not replacing, get the value */
623 value = cookie->value;
625 /* create value using 'closure' and 'makecb' */
626 value = makecb ? makecb(closure) : closure;
628 /* free previous value is needed */
629 if (cookie->value != value && cookie->freecb)
630 cookie->freecb(cookie->value);
632 /* if both value and freecb are NULL drop the cookie */
633 if (!value && !freecb) {
637 /* store the value and its releaser */
638 cookie->value = value;
639 cookie->freecb = freecb;
644 prv = &(cookie->next);
648 /* unlock the session and return the value */
649 session_unlock(session);
654 * Get the cookie of 'key' in the 'session'.
656 * @param session the session to search in
657 * @param key the key of the data to retrieve
659 * @return the data staored for the key or NULL if the key isn't found
661 void *afb_session_get_cookie(struct afb_session *session, const void *key)
663 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
667 * Set the cookie of 'key' in the 'session' to the 'value' that can be
668 * cleaned using 'freecb' (if not null).
670 * @param session the session to set
671 * @param key the key of the data to store
672 * @param value the value to store at key
673 * @param freecb a function to use when the cookie value is to remove (or null)
675 * @return 0 in case of success or -1 in case of error
677 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
679 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
683 * Set the language attached to the session
685 * @param session the session to set
686 * @param lang the language specifiction to set to session
688 * @return 0 in case of success or -1 in case of error
690 int afb_session_set_language(struct afb_session *session, const char *lang)
698 oldl = session->lang;
699 session->lang = newl;
705 * Get the language attached to the session
707 * @param session the session to query
708 * @param lang a default language specifiction
710 * @return the langauage specification to use for session
712 const char *afb_session_get_language(struct afb_session *session, const char *lang)
714 return session->lang ?: lang;