2 * Copyright (C) 2015-2019 "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"
31 #include "afb-token.h"
38 #define COOKIEMASK (COOKIECOUNT - 1)
40 #define _MAXEXP_ ((time_t)(~(time_t)0))
41 #define _MAXEXP2_ ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
42 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
43 #define NOW (time_now())
46 * structure for a cookie added to sessions
50 struct cookie *next; /**< link to next cookie */
51 const void *key; /**< pointer key */
52 void *value; /**< value */
53 void (*freecb)(void*); /**< function to call when session is closed */
57 * structure for session
61 struct afb_session *next; /**< link to the next */
62 unsigned refcount; /**< count of reference to the session */
63 int timeout; /**< timeout of the session */
64 time_t expiration; /**< expiration time of the token */
65 pthread_mutex_t mutex; /**< mutex of the session */
66 struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
67 char *lang; /**< current language setting for the session */
68 uint8_t closed: 1; /**< is the session closed ? */
69 uint8_t autoclose: 1; /**< close the session when unreferenced */
70 uint8_t notinset: 1; /**< session removed from the set of sessions */
71 uuid_stringz_t uuid; /**< long term authentication of remote client */
72 struct afb_token *token;/**< short term authentication of remote client */
76 * structure for managing sessions
79 int count; /**< current number of sessions */
80 int max; /**< maximum count of sessions */
81 int timeout; /**< common initial timeout */
82 struct afb_session *heads[HEADCOUNT]; /**< sessions */
83 struct afb_token *initok;/**< common initial token */
84 pthread_mutex_t mutex; /**< declare a mutex to protect hash table */
91 .mutex = PTHREAD_MUTEX_INITIALIZER
95 * Get the actual raw time
97 static inline time_t time_now()
100 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
104 /* lock the set of sessions for exclusive access */
105 static inline void sessionset_lock()
107 pthread_mutex_lock(&sessions.mutex);
110 /* unlock the set of sessions of exclusive access */
111 static inline void sessionset_unlock()
113 pthread_mutex_unlock(&sessions.mutex);
117 * search within the set of sessions the session of 'uuid'.
118 * 'hashidx' is the precomputed hash for 'uuid'
119 * return the session or NULL
121 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
123 struct afb_session *session;
125 session = sessions.heads[hashidx];
126 while (session && strcmp(uuid, session->uuid))
127 session = session->next;
132 /* add 'session' to the set of sessions */
133 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
135 /* check availability */
136 if (sessions.max && sessions.count >= sessions.max) {
141 /* add the session */
142 session->next = sessions.heads[hashidx];
143 sessions.heads[hashidx] = session;
148 /* make a new uuid not used in the set of sessions */
149 static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
154 uuid_new_stringz(uuid);
155 hashidx = pearson4(uuid);
156 } while(sessionset_search(uuid, hashidx));
160 /* lock the 'session' for exclusive access */
161 static inline void session_lock(struct afb_session *session)
163 pthread_mutex_lock(&session->mutex);
166 /* unlock the 'session' of exclusive access */
167 static inline void session_unlock(struct afb_session *session)
169 pthread_mutex_unlock(&session->mutex);
172 /* close the 'session' */
173 static void session_close(struct afb_session *session)
176 struct cookie *cookie;
178 /* close only one time */
179 if (!session->closed) {
185 afb_hook_session_close(session);
188 /* release cookies */
189 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
190 while ((cookie = session->cookies[idx])) {
191 session->cookies[idx] = cookie->next;
192 if (cookie->freecb != NULL)
193 cookie->freecb(cookie->value);
200 /* destroy the 'session' */
201 static void session_destroy (struct afb_session *session)
204 afb_hook_session_destroy(session);
206 pthread_mutex_destroy(&session->mutex);
207 afb_token_unref(session->token);
212 /* update expiration of 'session' according to 'now' */
213 static void session_update_expiration(struct afb_session *session, time_t now)
217 /* compute expiration */
218 expiration = now + afb_session_timeout(session);
220 expiration = MAX_EXPIRATION;
222 /* record the expiration */
223 session->expiration = expiration;
227 * Add a new session with the 'uuid' (of 'hashidx')
228 * and the 'timeout' starting from 'now'.
229 * Add it to the set of sessions
230 * Return the created session
232 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
234 struct afb_session *session;
236 /* check arguments */
237 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
238 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
243 /* allocates a new one */
244 session = calloc(1, sizeof *session);
245 if (session == NULL) {
251 pthread_mutex_init(&session->mutex, NULL);
252 session->refcount = 1;
253 strcpy(session->uuid, uuid);
254 session->token = afb_token_addref(sessions.initok);
255 session->timeout = timeout;
256 session_update_expiration(session, now);
259 if (sessionset_add(session, hashidx)) {
260 afb_token_unref(session->token);
266 afb_hook_session_create(session);
272 /* Remove expired sessions and return current time (now) */
273 static time_t sessionset_cleanup (int force)
275 struct afb_session *session, **prv;
279 /* Loop on Sessions Table and remove anything that is older than timeout */
281 for (idx = 0 ; idx < HEADCOUNT; idx++) {
282 prv = &sessions.heads[idx];
283 while ((session = *prv)) {
284 session_lock(session);
285 if (force || session->expiration < now)
286 session_close(session);
287 if (!session->closed)
288 prv = &session->next;
290 *prv = session->next;
292 session->notinset = 1;
293 if ( !session->refcount) {
294 session_destroy(session);
298 session_unlock(session);
305 * Initialize the session manager with a 'max_session_count',
306 * an initial common 'timeout' and an initial common token 'initok'.
308 * @param max_session_count maximum allowed session count in the same time
309 * @param timeout the initial default timeout of sessions
310 * @param initok the initial default token of sessions
313 int afb_session_init (int max_session_count, int timeout, const char *initok)
318 /* check parameters */
319 if (initok && strlen(initok) >= sizeof sessions.initok) {
320 ERROR("initial token '%s' too long (max length %d)",
321 initok, ((int)(sizeof sessions.initok)) - 1);
326 /* init the sessionset (after cleanup) */
328 sessionset_cleanup(1);
329 sessions.max = max_session_count;
330 sessions.timeout = timeout;
331 if (initok == NULL) {
332 uuid_new_stringz(uuid);
337 rc = afb_token_get(&sessions.initok, initok);
346 * Iterate the sessions and call 'callback' with
347 * the 'closure' for each session.
349 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
351 struct afb_session *session;
354 /* Loop on Sessions Table and remove anything that is older than timeout */
356 for (idx = 0 ; idx < HEADCOUNT; idx++) {
357 session = sessions.heads[idx];
359 if (!session->closed)
360 callback(closure, session);
361 session = session->next;
368 * Cleanup the sessionset of its closed or expired sessions
370 void afb_session_purge()
373 sessionset_cleanup(0);
378 * @return the initial token set at initialization
380 const char *afb_session_initial_token()
382 return sessions.initok ? afb_token_string(sessions.initok) : "";
385 /* Searchs the session of 'uuid' */
386 struct afb_session *afb_session_search (const char *uuid)
388 struct afb_session *session;
391 sessionset_cleanup(0);
392 session = sessionset_search(uuid, pearson4(uuid));
393 session = afb_session_addref(session);
400 * Creates a new session with 'timeout'
402 struct afb_session *afb_session_create (int timeout)
404 return afb_session_get(NULL, timeout, NULL);
408 * Returns the timeout of 'session' in seconds
410 int afb_session_timeout(struct afb_session *session)
414 /* compute timeout */
415 timeout = session->timeout;
416 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
417 timeout = sessions.timeout;
424 * Returns the second remaining before expiration of 'session'
426 int afb_session_what_remains(struct afb_session *session)
428 return (int)(session->expiration - NOW);
431 /* This function will return exiting session or newly created session */
432 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
434 uuid_stringz_t _uuid_;
436 struct afb_session *session;
442 now = sessionset_cleanup(0);
444 /* search for an existing one not too old */
446 hashidx = sessionset_make_uuid(_uuid_);
449 hashidx = pearson4(uuid);
450 session = sessionset_search(uuid, hashidx);
453 afb_session_addref(session);
458 /* create the session */
459 session = session_add(uuid, timeout, now, hashidx);
469 /* increase the use count on 'session' (can be NULL) */
470 struct afb_session *afb_session_addref(struct afb_session *session)
472 if (session != NULL) {
474 afb_hook_session_addref(session);
476 session_lock(session);
478 session_unlock(session);
483 /* decrease the use count of 'session' (can be NULL) */
484 void afb_session_unref(struct afb_session *session)
490 afb_hook_session_unref(session);
492 session_lock(session);
493 if (!--session->refcount) {
494 if (session->autoclose)
495 session_close(session);
496 if (session->notinset) {
497 session_destroy(session);
501 session_unlock(session);
504 /* close 'session' */
505 void afb_session_close (struct afb_session *session)
507 session_lock(session);
508 session_close(session);
509 session_unlock(session);
513 * Set the 'autoclose' flag of the 'session'
515 * A session whose autoclose flag is true will close as
516 * soon as it is no more referenced.
518 * @param session the session to set
519 * @param autoclose the value to set
521 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
523 session->autoclose = !!autoclose;
526 /* is 'session' closed? */
527 int afb_session_is_closed (struct afb_session *session)
529 return session->closed;
533 * check whether the token of 'session' is 'token'
534 * return 1 if true or 0 otherwise
536 int afb_session_check_token (struct afb_session *session, const char *token)
540 session_lock(session);
542 && session->expiration >= NOW
543 && !(session->token && strcmp(token, afb_token_string(session->token)));
544 session_unlock(session);
548 /* Returns the uuid of 'session' */
549 const char *afb_session_uuid (struct afb_session *session)
551 return session->uuid;
555 * Get the index of the 'key' in the cookies array.
556 * @param key the key to scan
557 * @return the index of the list for key within cookies
559 static int cookeyidx(const void *key)
561 intptr_t x = (intptr_t)key;
562 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
563 return r & COOKIEMASK;
567 * Set, get, replace, remove a cookie of 'key' for the 'session'
569 * The behaviour of this function depends on its parameters:
571 * @param session the session
572 * @param key the key of the cookie
573 * @param makecb the creation function or NULL
574 * @param freecb the release function or NULL
575 * @param closure an argument for makecb or the value if makecb==NULL
576 * @param replace a boolean enforcing replacement of the previous value
578 * @return the value of the cookie
580 * The 'key' is a pointer and compared as pointers.
582 * For getting the current value of the cookie:
584 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
586 * For storing the value of the cookie
588 * afb_session_cookie(session, key, NULL, NULL, value, 1)
590 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
594 struct cookie *cookie, **prv;
596 /* get key hashed index */
597 idx = cookeyidx(key);
599 /* lock session and search for the cookie of 'key' */
600 session_lock(session);
601 prv = &session->cookies[idx];
605 /* 'key' not found, create value using 'closure' and 'makecb' */
606 value = makecb ? makecb(closure) : closure;
607 /* store the the only if it has some meaning */
608 if (replace || makecb || freecb) {
609 cookie = malloc(sizeof *cookie);
612 /* calling freecb if there is no makecb may have issue */
613 if (makecb && freecb)
618 cookie->value = value;
619 cookie->freecb = freecb;
625 } else if (cookie->key == key) {
626 /* cookie of key found */
628 /* not replacing, get the value */
629 value = cookie->value;
631 /* create value using 'closure' and 'makecb' */
632 value = makecb ? makecb(closure) : closure;
634 /* free previous value is needed */
635 if (cookie->value != value && cookie->freecb)
636 cookie->freecb(cookie->value);
638 /* if both value and freecb are NULL drop the cookie */
639 if (!value && !freecb) {
643 /* store the value and its releaser */
644 cookie->value = value;
645 cookie->freecb = freecb;
650 prv = &(cookie->next);
654 /* unlock the session and return the value */
655 session_unlock(session);
660 * Get the cookie of 'key' in the 'session'.
662 * @param session the session to search in
663 * @param key the key of the data to retrieve
665 * @return the data staored for the key or NULL if the key isn't found
667 void *afb_session_get_cookie(struct afb_session *session, const void *key)
669 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
673 * Set the cookie of 'key' in the 'session' to the 'value' that can be
674 * cleaned using 'freecb' (if not null).
676 * @param session the session to set
677 * @param key the key of the data to store
678 * @param value the value to store at key
679 * @param freecb a function to use when the cookie value is to remove (or null)
681 * @return 0 in case of success or -1 in case of error
683 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
685 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
689 * Set the language attached to the session
691 * @param session the session to set
692 * @param lang the language specifiction to set to session
694 * @return 0 in case of success or -1 in case of error
696 int afb_session_set_language(struct afb_session *session, const char *lang)
704 oldl = session->lang;
705 session->lang = newl;
711 * Get the language attached to the session
713 * @param session the session to query
714 * @param lang a default language specifiction
716 * @return the langauage specification to use for session
718 const char *afb_session_get_language(struct afb_session *session, const char *lang)
720 return session->lang ?: lang;