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"
36 #define SESSION_COUNT_MIN 5
37 #define SESSION_COUNT_MAX 1000
40 * Handling of cookies.
41 * Cookies are stored by session.
42 * The cookie count COOKIECOUNT must be a power of 2, possible values: 1, 2, 4, 8, 16, 32, 64, ...
43 * For low memory profile, small values are better, 1 is possible.
46 #define COOKIEMASK (COOKIECOUNT - 1)
48 #define _MAXEXP_ ((time_t)(~(time_t)0))
49 #define _MAXEXP2_ ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
50 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
51 #define NOW (time_now())
54 * structure for a cookie added to sessions
58 struct cookie *next; /**< link to next cookie */
59 const void *key; /**< pointer key */
60 void *value; /**< value */
61 void (*freecb)(void*); /**< function to call when session is closed */
65 * structure for session
69 struct afb_session *next; /**< link to the next */
70 uint16_t refcount; /**< count of reference to the session */
71 uint16_t id; /**< local id of the session */
72 int timeout; /**< timeout of the session */
73 time_t expiration; /**< expiration time of the session */
74 pthread_mutex_t mutex; /**< mutex of the session */
75 struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
76 char *lang; /**< current language setting for the session */
77 uint8_t closed: 1; /**< is the session closed ? */
78 uint8_t autoclose: 1; /**< close the session when unreferenced */
79 uint8_t notinset: 1; /**< session removed from the set of sessions */
80 uint8_t hash; /**< hash value of the uuid */
81 uuid_stringz_t uuid; /**< identification of client session */
85 * structure for managing sessions
88 uint16_t count; /**< current number of sessions */
89 uint16_t max; /**< maximum count of sessions */
90 uint16_t genid; /**< for generating ids */
91 int timeout; /**< common initial timeout */
92 struct afb_session *first; /**< sessions */
93 struct afb_token *initok;/**< common initial token */
94 pthread_mutex_t mutex; /**< declare a mutex to protect hash table */
102 .mutex = PTHREAD_MUTEX_INITIALIZER
106 * Get the actual raw time
108 static inline time_t time_now()
111 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
115 /* lock the set of sessions for exclusive access */
116 static inline void sessionset_lock()
118 pthread_mutex_lock(&sessions.mutex);
121 /* unlock the set of sessions of exclusive access */
122 static inline void sessionset_unlock()
124 pthread_mutex_unlock(&sessions.mutex);
128 * search within the set of sessions the session of 'uuid'.
129 * 'hashidx' is the precomputed hash for 'uuid'
130 * return the session or NULL
132 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
134 struct afb_session *session;
136 session = sessions.first;
137 while (session && hashidx != session->hash && strcmp(uuid, session->uuid))
138 session = session->next;
144 * search within the set of sessions the session of 'id'.
145 * return the session or NULL
147 static struct afb_session *sessionset_search_id(uint16_t id)
149 struct afb_session *session;
151 session = sessions.first;
152 while (session && id != session->id)
153 session = session->next;
158 /* add 'session' to the set of sessions */
159 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
161 /* check availability */
162 if (sessions.max && sessions.count >= sessions.max) {
167 /* add the session */
168 session->next = sessions.first;
169 sessions.first = session;
174 /* make a new uuid not used in the set of sessions */
175 static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
180 uuid_new_stringz(uuid);
181 hashidx = pearson4(uuid);
182 } while(sessionset_search(uuid, hashidx));
186 /* lock the 'session' for exclusive access */
187 static inline void session_lock(struct afb_session *session)
189 pthread_mutex_lock(&session->mutex);
192 /* unlock the 'session' of exclusive access */
193 static inline void session_unlock(struct afb_session *session)
195 pthread_mutex_unlock(&session->mutex);
198 /* close the 'session' */
199 static void session_close(struct afb_session *session)
202 struct cookie *cookie;
204 /* close only one time */
205 if (!session->closed) {
211 afb_hook_session_close(session);
214 /* release cookies */
215 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
216 while ((cookie = session->cookies[idx])) {
217 session->cookies[idx] = cookie->next;
218 if (cookie->freecb != NULL)
219 cookie->freecb(cookie->value);
226 /* destroy the 'session' */
227 static void session_destroy (struct afb_session *session)
230 afb_hook_session_destroy(session);
232 pthread_mutex_destroy(&session->mutex);
237 /* update expiration of 'session' according to 'now' */
238 static void session_update_expiration(struct afb_session *session, time_t now)
242 /* compute expiration */
243 expiration = now + afb_session_timeout(session);
245 expiration = MAX_EXPIRATION;
247 /* record the expiration */
248 session->expiration = expiration;
252 * Add a new session with the 'uuid' (of 'hashidx')
253 * and the 'timeout' starting from 'now'.
254 * Add it to the set of sessions
255 * Return the created session
257 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
259 struct afb_session *session;
261 /* check arguments */
262 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
263 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
268 /* allocates a new one */
269 session = calloc(1, sizeof *session);
270 if (session == NULL) {
276 pthread_mutex_init(&session->mutex, NULL);
277 session->refcount = 1;
278 strcpy(session->uuid, uuid);
279 session->timeout = timeout;
280 session_update_expiration(session, now);
281 session->id = ++sessions.genid;
282 while (session->id == 0 || sessionset_search_id(session->id) != NULL)
283 session->id = ++sessions.genid;
286 if (sessionset_add(session, hashidx)) {
292 afb_hook_session_create(session);
298 /* Remove expired sessions and return current time (now) */
299 static time_t sessionset_cleanup (int force)
301 struct afb_session *session, **prv;
304 /* Loop on Sessions Table and remove anything that is older than timeout */
306 prv = &sessions.first;
307 while ((session = *prv)) {
308 session_lock(session);
309 if (force || session->expiration < now)
310 session_close(session);
311 if (!session->closed) {
312 prv = &session->next;
313 session_unlock(session);
315 *prv = session->next;
317 session->notinset = 1;
318 if (session->refcount)
319 session_unlock(session);
321 session_destroy(session);
328 * Initialize the session manager with a 'max_session_count',
329 * an initial common 'timeout' and an initial common token 'initok'.
331 * @param max_session_count maximum allowed session count in the same time
332 * @param timeout the initial default timeout of sessions
333 * @param initok the initial default token of sessions
335 int afb_session_init (int max_session_count, int timeout, const char *initok)
340 /* check parameters */
341 if (initok && strlen(initok) >= sizeof sessions.initok) {
342 ERROR("initial token '%s' too long (max length %d)",
343 initok, ((int)(sizeof sessions.initok)) - 1);
348 /* init the sessionset (after cleanup) */
350 sessionset_cleanup(1);
351 if (max_session_count > SESSION_COUNT_MAX)
352 sessions.max = SESSION_COUNT_MAX;
353 else if (max_session_count < SESSION_COUNT_MIN)
354 sessions.max = SESSION_COUNT_MIN;
356 sessions.max = (uint16_t)max_session_count;
357 sessions.timeout = timeout;
358 if (initok == NULL) {
359 uuid_new_stringz(uuid);
364 rc = afb_token_get(&sessions.initok, initok);
373 * Iterate the sessions and call 'callback' with
374 * the 'closure' for each session.
376 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
378 struct afb_session *session;
380 /* Loop on Sessions Table and remove anything that is older than timeout */
382 session = sessions.first;
384 if (!session->closed)
385 callback(closure, session);
386 session = session->next;
392 * Cleanup the sessionset of its closed or expired sessions
394 void afb_session_purge()
397 sessionset_cleanup(0);
402 * @return the initial token set at initialization
404 const char *afb_session_initial_token()
406 return sessions.initok ? afb_token_string(sessions.initok) : "";
409 /* Searchs the session of 'uuid' */
410 struct afb_session *afb_session_search (const char *uuid)
412 struct afb_session *session;
415 sessionset_cleanup(0);
416 session = sessionset_search(uuid, pearson4(uuid));
417 session = afb_session_addref(session);
424 * Creates a new session with 'timeout'
426 struct afb_session *afb_session_create (int timeout)
428 return afb_session_get(NULL, timeout, NULL);
432 * Returns the timeout of 'session' in seconds
434 int afb_session_timeout(struct afb_session *session)
438 /* compute timeout */
439 timeout = session->timeout;
440 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
441 timeout = sessions.timeout;
448 * Returns the second remaining before expiration of 'session'
450 int afb_session_what_remains(struct afb_session *session)
452 int diff = (int)(session->expiration - NOW);
453 return diff < 0 ? 0 : diff;
456 /* This function will return exiting session or newly created session */
457 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
459 uuid_stringz_t _uuid_;
461 struct afb_session *session;
467 now = sessionset_cleanup(0);
469 /* search for an existing one not too old */
471 hashidx = sessionset_make_uuid(_uuid_);
474 hashidx = pearson4(uuid);
475 session = sessionset_search(uuid, hashidx);
478 afb_session_addref(session);
483 /* create the session */
484 session = session_add(uuid, timeout, now, hashidx);
494 /* increase the use count on 'session' (can be NULL) */
495 struct afb_session *afb_session_addref(struct afb_session *session)
497 if (session != NULL) {
499 afb_hook_session_addref(session);
501 session_lock(session);
503 session_unlock(session);
508 /* decrease the use count of 'session' (can be NULL) */
509 void afb_session_unref(struct afb_session *session)
515 afb_hook_session_unref(session);
517 session_lock(session);
518 if (!--session->refcount) {
519 if (session->autoclose)
520 session_close(session);
521 if (session->notinset) {
522 session_destroy(session);
526 session_unlock(session);
529 /* close 'session' */
530 void afb_session_close (struct afb_session *session)
532 session_lock(session);
533 session_close(session);
534 session_unlock(session);
538 * Set the 'autoclose' flag of the 'session'
540 * A session whose autoclose flag is true will close as
541 * soon as it is no more referenced.
543 * @param session the session to set
544 * @param autoclose the value to set
546 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
548 session->autoclose = !!autoclose;
551 /* is 'session' closed? */
552 int afb_session_is_closed (struct afb_session *session)
554 return session->closed;
557 /* Returns the uuid of 'session' */
558 const char *afb_session_uuid (struct afb_session *session)
560 return session->uuid;
563 /* Returns the local id of 'session' */
564 uint16_t afb_session_id (struct afb_session *session)
570 * Get the index of the 'key' in the cookies array.
571 * @param key the key to scan
572 * @return the index of the list for key within cookies
575 static int cookeyidx(const void *key)
577 intptr_t x = (intptr_t)key;
578 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
579 return r & COOKIEMASK;
582 # define cookeyidx(key) 0
586 * Set, get, replace, remove a cookie of 'key' for the 'session'
588 * The behaviour of this function depends on its parameters:
590 * @param session the session
591 * @param key the key of the cookie
592 * @param makecb the creation function or NULL
593 * @param freecb the release function or NULL
594 * @param closure an argument for makecb or the value if makecb==NULL
595 * @param replace a boolean enforcing replacement of the previous value
597 * @return the value of the cookie
599 * The 'key' is a pointer and compared as pointers.
601 * For getting the current value of the cookie:
603 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
605 * For storing the value of the cookie
607 * afb_session_cookie(session, key, NULL, NULL, value, 1)
609 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
613 struct cookie *cookie, **prv;
615 /* get key hashed index */
616 idx = cookeyidx(key);
618 /* lock session and search for the cookie of 'key' */
619 session_lock(session);
620 prv = &session->cookies[idx];
624 /* 'key' not found, create value using 'closure' and 'makecb' */
625 value = makecb ? makecb(closure) : closure;
626 /* store the the only if it has some meaning */
627 if (replace || makecb || freecb) {
628 cookie = malloc(sizeof *cookie);
631 /* calling freecb if there is no makecb may have issue */
632 if (makecb && freecb)
637 cookie->value = value;
638 cookie->freecb = freecb;
644 } else if (cookie->key == key) {
645 /* cookie of key found */
647 /* not replacing, get the value */
648 value = cookie->value;
650 /* create value using 'closure' and 'makecb' */
651 value = makecb ? makecb(closure) : closure;
653 /* free previous value is needed */
654 if (cookie->value != value && cookie->freecb)
655 cookie->freecb(cookie->value);
657 /* if both value and freecb are NULL drop the cookie */
658 if (!value && !freecb) {
662 /* store the value and its releaser */
663 cookie->value = value;
664 cookie->freecb = freecb;
669 prv = &(cookie->next);
673 /* unlock the session and return the value */
674 session_unlock(session);
679 * Get the cookie of 'key' in the 'session'.
681 * @param session the session to search in
682 * @param key the key of the data to retrieve
684 * @return the data staored for the key or NULL if the key isn't found
686 void *afb_session_get_cookie(struct afb_session *session, const void *key)
688 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
692 * Set the cookie of 'key' in the 'session' to the 'value' that can be
693 * cleaned using 'freecb' (if not null).
695 * @param session the session to set
696 * @param key the key of the data to store
697 * @param value the value to store at key
698 * @param freecb a function to use when the cookie value is to remove (or null)
700 * @return 0 in case of success or -1 in case of error
702 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
704 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
708 * Set the language attached to the session
710 * @param session the session to set
711 * @param lang the language specifiction to set to session
713 * @return 0 in case of success or -1 in case of error
715 int afb_session_set_language(struct afb_session *session, const char *lang)
723 oldl = session->lang;
724 session->lang = newl;
730 * Get the language attached to the session
732 * @param session the session to query
733 * @param lang a default language specifiction
735 * @return the langauage specification to use for session
737 const char *afb_session_get_language(struct afb_session *session, const char *lang)
739 return session->lang ?: lang;