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.
27 #include <uuid/uuid.h>
31 #include "afb-session.h"
39 #define COOKIEMASK (COOKIECOUNT - 1)
41 #define _MAXEXP_ ((time_t)(~(time_t)0))
42 #define _MAXEXP2_ ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
43 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
44 #define NOW (time_now())
47 * structure for a cookie added to sessions
51 struct cookie *next; /**< link to next cookie */
52 const void *key; /**< pointer key */
53 void *value; /**< value */
54 void (*freecb)(void*); /**< function to call when session is closed */
58 * structure for session
62 struct afb_session *next; /**< link to the next */
63 unsigned refcount; /**< count of reference to the session */
64 int timeout; /**< timeout of the session */
65 time_t expiration; /**< expiration time of the token */
66 pthread_mutex_t mutex; /**< mutex of the session */
67 struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
68 char *lang; /**< current language setting for the session */
69 uint8_t closed: 1; /**< is the session closed ? */
70 uint8_t autoclose: 1; /**< close the session when unreferenced */
71 uint8_t notinset: 1; /**< session removed from the set of sessions */
72 char uuid[SIZEUUID]; /**< long term authentication of remote client */
73 char token[SIZEUUID]; /**< short term authentication of remote client */
77 * structure for managing sessions
80 int count; /**< current number of sessions */
81 int max; /**< maximum count of sessions */
82 int timeout; /**< common initial timeout */
83 struct afb_session *heads[HEADCOUNT]; /**< sessions */
84 char initok[SIZEUUID]; /**< common initial token */
85 pthread_mutex_t mutex; /**< declare a mutex to protect hash table */
92 .mutex = PTHREAD_MUTEX_INITIALIZER
96 * Get the actual raw time
98 static inline time_t time_now()
101 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
106 * generate a new fresh 'uuid'
108 static void new_uuid(char uuid[SIZEUUID])
112 #if defined(USE_UUID_GENERATE)
113 uuid_generate(newuuid);
117 static uint16_t counter;
118 static char state[32];
119 static struct random_data rdata;
122 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
124 pid = (uint16_t)getpid();
125 counter = (uint16_t)(ts.tv_nsec >> 8);
127 initstate_r((((unsigned)pid) << 16) + ((unsigned)counter),
128 state, sizeof state, &rdata);
130 ts.tv_nsec ^= (long)ts.tv_sec;
134 newuuid[0] = (char)(ts.tv_nsec >> 24);
135 newuuid[1] = (char)(ts.tv_nsec >> 16);
136 newuuid[2] = (char)(ts.tv_nsec >> 8);
137 newuuid[3] = (char)(ts.tv_nsec);
139 newuuid[4] = (char)(pid >> 8);
140 newuuid[5] = (char)(pid);
142 random_r(&rdata, &x);
143 newuuid[6] = (char)(((x >> 16) & 0x0f) | 0x40); /* pseudo-random version */
144 newuuid[7] = (char)(x >> 8);
146 random_r(&rdata, &x);
147 newuuid[8] = (char)(((x >> 16) & 0x3f) | 0x80); /* variant RFC4122 */
148 newuuid[9] = (char)(x >> 8);
150 random_r(&rdata, &x);
151 newuuid[10] = (char)(x >> 16);
152 newuuid[11] = (char)(x >> 8);
154 random_r(&rdata, &x);
155 newuuid[12] = (char)(x >> 16);
156 newuuid[13] = (char)(x >> 8);
158 newuuid[14] = (char)(counter >> 8);
159 newuuid[15] = (char)(counter);
161 uuid_unparse_lower(newuuid, uuid);
164 /* lock the set of sessions for exclusive access */
165 static inline void sessionset_lock()
167 pthread_mutex_lock(&sessions.mutex);
170 /* unlock the set of sessions of exclusive access */
171 static inline void sessionset_unlock()
173 pthread_mutex_unlock(&sessions.mutex);
177 * search within the set of sessions the session of 'uuid'.
178 * 'hashidx' is the precomputed hash for 'uuid'
179 * return the session or NULL
181 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
183 struct afb_session *session;
185 session = sessions.heads[hashidx];
186 while (session && strcmp(uuid, session->uuid))
187 session = session->next;
192 /* add 'session' to the set of sessions */
193 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
195 /* check availability */
196 if (sessions.max && sessions.count >= sessions.max) {
201 /* add the session */
202 session->next = sessions.heads[hashidx];
203 sessions.heads[hashidx] = session;
208 /* make a new uuid not used in the set of sessions */
209 static uint8_t sessionset_make_uuid (char uuid[SIZEUUID])
215 hashidx = pearson4(uuid);
216 } while(sessionset_search(uuid, hashidx));
220 /* lock the 'session' for exclusive access */
221 static inline void session_lock(struct afb_session *session)
223 pthread_mutex_lock(&session->mutex);
226 /* unlock the 'session' of exclusive access */
227 static inline void session_unlock(struct afb_session *session)
229 pthread_mutex_unlock(&session->mutex);
232 /* close the 'session' */
233 static void session_close(struct afb_session *session)
236 struct cookie *cookie;
238 /* close only one time */
239 if (!session->closed) {
244 afb_hook_session_close(session);
246 /* release cookies */
247 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
248 while ((cookie = session->cookies[idx])) {
249 session->cookies[idx] = cookie->next;
250 if (cookie->freecb != NULL)
251 cookie->freecb(cookie->value);
258 /* destroy the 'session' */
259 static void session_destroy (struct afb_session *session)
261 afb_hook_session_destroy(session);
262 pthread_mutex_destroy(&session->mutex);
267 /* update expiration of 'session' according to 'now' */
268 static void session_update_expiration(struct afb_session *session, time_t now)
272 /* compute expiration */
273 expiration = now + afb_session_timeout(session);
275 expiration = MAX_EXPIRATION;
277 /* record the expiration */
278 session->expiration = expiration;
282 * Add a new session with the 'uuid' (of 'hashidx')
283 * and the 'timeout' starting from 'now'.
284 * Add it to the set of sessions
285 * Return the created session
287 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
289 struct afb_session *session;
291 /* check arguments */
292 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
293 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
298 /* allocates a new one */
299 session = calloc(1, sizeof *session);
300 if (session == NULL) {
306 pthread_mutex_init(&session->mutex, NULL);
307 session->refcount = 1;
308 strcpy(session->uuid, uuid);
309 strcpy(session->token, sessions.initok);
310 session->timeout = timeout;
311 session_update_expiration(session, now);
314 if (sessionset_add(session, hashidx)) {
319 afb_hook_session_create(session);
324 /* Remove expired sessions and return current time (now) */
325 static time_t sessionset_cleanup (int force)
327 struct afb_session *session, **prv;
331 /* Loop on Sessions Table and remove anything that is older than timeout */
333 for (idx = 0 ; idx < HEADCOUNT; idx++) {
334 prv = &sessions.heads[idx];
335 while ((session = *prv)) {
336 session_lock(session);
337 if (force || session->expiration < now)
338 session_close(session);
339 if (!session->closed)
340 prv = &session->next;
342 *prv = session->next;
344 session->notinset = 1;
345 if ( !session->refcount) {
346 session_destroy(session);
350 session_unlock(session);
357 * Initialize the session manager with a 'max_session_count',
358 * an initial common 'timeout' and an initial common token 'initok'.
360 * @param max_session_count maximum allowed session count in the same time
361 * @param timeout the initial default timeout of sessions
362 * @param initok the initial default token of sessions
365 int afb_session_init (int max_session_count, int timeout, const char *initok)
367 /* check parameters */
368 if (initok && strlen(initok) >= sizeof sessions.initok) {
369 ERROR("initial token '%s' too long (max length %d)",
370 initok, ((int)(sizeof sessions.initok)) - 1);
375 /* init the sessionset (after cleanup) */
377 sessionset_cleanup(1);
378 sessions.max = max_session_count;
379 sessions.timeout = timeout;
381 new_uuid(sessions.initok);
383 strcpy(sessions.initok, initok);
389 * Iterate the sessions and call 'callback' with
390 * the 'closure' for each session.
392 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
394 struct afb_session *session;
397 /* Loop on Sessions Table and remove anything that is older than timeout */
399 for (idx = 0 ; idx < HEADCOUNT; idx++) {
400 session = sessions.heads[idx];
402 if (!session->closed)
403 callback(closure, session);
404 session = session->next;
411 * Cleanup the sessionset of its closed or expired sessions
413 void afb_session_purge()
416 sessionset_cleanup(0);
421 * @return the initial token set at initialization
423 const char *afb_session_initial_token()
425 return sessions.initok;
428 /* Searchs the session of 'uuid' */
429 struct afb_session *afb_session_search (const char *uuid)
431 struct afb_session *session;
434 sessionset_cleanup(0);
435 session = sessionset_search(uuid, pearson4(uuid));
436 session = afb_session_addref(session);
443 * Creates a new session with 'timeout'
445 struct afb_session *afb_session_create (int timeout)
447 return afb_session_get(NULL, timeout, NULL);
451 * Returns the timeout of 'session' in seconds
453 int afb_session_timeout(struct afb_session *session)
457 /* compute timeout */
458 timeout = session->timeout;
459 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
460 timeout = sessions.timeout;
467 * Returns the second remaining before expiration of 'session'
469 int afb_session_what_remains(struct afb_session *session)
471 return (int)(session->expiration - NOW);
474 /* This function will return exiting session or newly created session */
475 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
477 char _uuid_[SIZEUUID];
479 struct afb_session *session;
485 now = sessionset_cleanup(0);
487 /* search for an existing one not too old */
489 hashidx = sessionset_make_uuid(_uuid_);
492 hashidx = pearson4(uuid);
493 session = sessionset_search(uuid, hashidx);
496 afb_session_addref(session);
501 /* create the session */
502 session = session_add(uuid, timeout, now, hashidx);
512 /* increase the use count on 'session' (can be NULL) */
513 struct afb_session *afb_session_addref(struct afb_session *session)
515 if (session != NULL) {
516 afb_hook_session_addref(session);
517 session_lock(session);
519 session_unlock(session);
524 /* decrease the use count of 'session' (can be NULL) */
525 void afb_session_unref(struct afb_session *session)
530 afb_hook_session_unref(session);
531 session_lock(session);
532 if (!--session->refcount) {
533 if (session->autoclose)
534 session_close(session);
535 if (session->notinset) {
536 session_destroy(session);
540 session_unlock(session);
543 /* close 'session' */
544 void afb_session_close (struct afb_session *session)
546 session_lock(session);
547 session_close(session);
548 session_unlock(session);
552 * Set the 'autoclose' flag of the 'session'
554 * A session whose autoclose flag is true will close as
555 * soon as it is no more referenced.
557 * @param session the session to set
558 * @param autoclose the value to set
560 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
562 session->autoclose = !!autoclose;
565 /* is 'session' closed? */
566 int afb_session_is_closed (struct afb_session *session)
568 return session->closed;
572 * check whether the token of 'session' is 'token'
573 * return 1 if true or 0 otherwise
575 int afb_session_check_token (struct afb_session *session, const char *token)
579 session_lock(session);
581 && session->expiration >= NOW
582 && !(session->token[0] && strcmp (token, session->token));
583 session_unlock(session);
587 /* generate a new token and update client context */
588 void afb_session_new_token (struct afb_session *session)
590 session_lock(session);
591 new_uuid(session->token);
592 session_update_expiration(session, NOW);
593 afb_hook_session_renew(session);
594 session_unlock(session);
597 /* Returns the uuid of 'session' */
598 const char *afb_session_uuid (struct afb_session *session)
600 return session->uuid;
603 /* Returns the token of 'session' */
604 const char *afb_session_token (struct afb_session *session)
606 return session->token;
610 * Get the index of the 'key' in the cookies array.
611 * @param key the key to scan
612 * @return the index of the list for key within cookies
614 static int cookeyidx(const void *key)
616 intptr_t x = (intptr_t)key;
617 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
618 return r & COOKIEMASK;
622 * Set, get, replace, remove a cookie of 'key' for the 'session'
624 * The behaviour of this function depends on its parameters:
626 * @param session the session
627 * @param key the key of the cookie
628 * @param makecb the creation function or NULL
629 * @param freecb the release function or NULL
630 * @param closure an argument for makecb or the value if makecb==NULL
631 * @param replace a boolean enforcing replecement of the previous value
633 * @return the value of the cookie
635 * The 'key' is a pointer and compared as pointers.
637 * For getting the current value of the cookie:
639 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
641 * For storing the value of the cookie
643 * afb_session_cookie(session, key, NULL, NULL, value, 1)
645 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
649 struct cookie *cookie, **prv;
651 /* get key hashed index */
652 idx = cookeyidx(key);
654 /* lock session and search for the cookie of 'key' */
655 session_lock(session);
656 prv = &session->cookies[idx];
660 /* 'key' not found, create value using 'closure' and 'makecb' */
661 value = makecb ? makecb(closure) : closure;
662 /* store the the only if it has some meaning */
663 if (replace || makecb || freecb) {
664 cookie = malloc(sizeof *cookie);
667 /* calling freecb if there is no makecb may have issue */
668 if (makecb && freecb)
673 cookie->value = value;
674 cookie->freecb = freecb;
680 } else if (cookie->key == key) {
681 /* cookie of key found */
683 /* not replacing, get the value */
684 value = cookie->value;
686 /* create value using 'closure' and 'makecb' */
687 value = makecb ? makecb(closure) : closure;
689 /* free previous value is needed */
690 if (cookie->value != value && cookie->freecb)
691 cookie->freecb(cookie->value);
693 /* if both value and freecb are NULL drop the cookie */
694 if (!value && !freecb) {
698 /* store the value and its releaser */
699 cookie->value = value;
700 cookie->freecb = freecb;
705 prv = &(cookie->next);
709 /* unlock the session and return the value */
710 session_unlock(session);
715 * Get the cookie of 'key' in the 'session'.
717 * @param session the session to search in
718 * @param key the key of the data to retrieve
720 * @return the data staored for the key or NULL if the key isn't found
722 void *afb_session_get_cookie(struct afb_session *session, const void *key)
724 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
728 * Set the cookie of 'key' in the 'session' to the 'value' that can be
729 * cleaned using 'freecb' (if not null).
731 * @param session the session to set
732 * @param key the key of the data to store
733 * @param value the value to store at key
734 * @param freecb a function to use when the cookie value is to remove (or null)
736 * @return 0 in case of success or -1 in case of error
738 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
740 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
744 * Set the language attached to the session
746 * @param session the session to set
747 * @param lang the language specifiction to set to session
749 * @return 0 in case of success or -1 in case of error
751 int afb_session_set_language(struct afb_session *session, const char *lang)
759 oldl = session->lang;
760 session->lang = newl;
766 * Get the language attached to the session
768 * @param session the session to query
769 * @param lang a default language specifiction
771 * @return the langauage specification to use for session
773 const char *afb_session_get_language(struct afb_session *session, const char *lang)
775 return session->lang ?: lang;