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.
27 #include <uuid/uuid.h>
30 #include <json-c/json.h>
32 #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())
46 /* 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; /* external reference count of 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 uint8_t closed: 1; /* is the session closed ? */
67 uint8_t autoclose: 1; /* close the session when unreferenced */
68 uint8_t notinset: 1; /* session removed from the set of sessions */
69 char uuid[SIZEUUID]; /* long term authentication of remote client */
70 char token[SIZEUUID]; /* short term authentication of remote client */
73 /* Session UUID are store in a simple array [for 10 sessions this should be enough] */
75 int count; /* current number of sessions */
76 int max; /* maximum count of sessions */
77 int timeout; /* common initial timeout */
78 struct afb_session *heads[HEADCOUNT]; /* sessions */
79 char initok[SIZEUUID]; /* common initial token */
80 pthread_mutex_t mutex; /* declare a mutex to protect hash table */
87 .mutex = PTHREAD_MUTEX_INITIALIZER
90 /* Get the actual raw time */
91 static inline time_t time_now()
94 clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
98 /* generate a new fresh 'uuid' */
99 static void new_uuid(char uuid[SIZEUUID])
102 uuid_generate(newuuid);
103 uuid_unparse_lower(newuuid, uuid);
107 * Returns a tiny hash value for the 'text'.
109 * Tiny hash function inspired from pearson
111 static uint8_t pearson4(const char *text)
113 static uint8_t T[16] = {
114 4, 1, 6, 0, 9, 14, 11, 5,
115 2, 3, 12, 15, 10, 7, 8, 13
119 for (r = 0; (c = (uint8_t)*text) ; text++) {
123 return r; // % HEADCOUNT;
126 /* lock the set of sessions for exclusive access */
127 static inline void sessionset_lock()
129 pthread_mutex_lock(&sessions.mutex);
132 /* unlock the set of sessions of exclusive access */
133 static inline void sessionset_unlock()
135 pthread_mutex_unlock(&sessions.mutex);
139 * search within the set of sessions the session of 'uuid'.
140 * 'hashidx' is the precomputed hash for 'uuid'
141 * return the session or NULL
143 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
145 struct afb_session *session;
147 session = sessions.heads[hashidx];
148 while (session && strcmp(uuid, session->uuid))
149 session = session->next;
154 /* add 'session' to the set of sessions */
155 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
157 /* check availability */
158 if (sessions.count >= sessions.max) {
163 /* add the session */
164 session->next = sessions.heads[hashidx];
165 sessions.heads[hashidx] = session;
170 /* make a new uuid not used in the set of sessions */
171 static uint8_t sessionset_make_uuid (char uuid[SIZEUUID])
177 hashidx = pearson4(uuid);
178 } while(sessionset_search(uuid, hashidx));
182 /* lock the 'session' for exclusive access */
183 static inline void session_lock(struct afb_session *session)
185 pthread_mutex_lock(&session->mutex);
188 /* unlock the 'session' of exclusive access */
189 static inline void session_unlock(struct afb_session *session)
191 pthread_mutex_unlock(&session->mutex);
194 /* close the 'session' */
195 static void session_close(struct afb_session *session)
198 struct cookie *cookie;
200 /* close only one time */
201 if (!session->closed) {
206 afb_hook_session_close(session);
208 /* release cookies */
209 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
210 while ((cookie = session->cookies[idx])) {
211 session->cookies[idx] = cookie->next;
212 if (cookie->freecb != NULL)
213 cookie->freecb(cookie->value);
220 /* destroy the 'session' */
221 static void session_destroy (struct afb_session *session)
223 afb_hook_session_destroy(session);
224 pthread_mutex_destroy(&session->mutex);
228 /* update expiration of 'session' according to 'now' */
229 static void session_update_expiration(struct afb_session *session, time_t now)
233 /* compute expiration */
234 expiration = now + afb_session_timeout(session);
236 expiration = MAX_EXPIRATION;
238 /* record the expiration */
239 session->expiration = expiration;
243 * Add a new session with the 'uuid' (of 'hashidx')
244 * and the 'timeout' starting from 'now'.
245 * Add it to the set of sessions
246 * Return the created session
248 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
250 struct afb_session *session;
252 /* check arguments */
253 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
254 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
259 /* allocates a new one */
260 session = calloc(1, sizeof *session);
261 if (session == NULL) {
267 pthread_mutex_init(&session->mutex, NULL);
268 session->refcount = 1;
269 strcpy(session->uuid, uuid);
270 strcpy(session->token, sessions.initok);
271 session->timeout = timeout;
272 session_update_expiration(session, now);
275 if (sessionset_add(session, hashidx)) {
280 afb_hook_session_create(session);
285 /* Remove expired sessions and return current time (now) */
286 static time_t sessionset_cleanup (int force)
288 struct afb_session *session, **prv;
292 /* Loop on Sessions Table and remove anything that is older than timeout */
294 for (idx = 0 ; idx < HEADCOUNT; idx++) {
295 prv = &sessions.heads[idx];
296 while ((session = *prv)) {
297 session_lock(session);
298 if (force || session->expiration < now)
299 session_close(session);
300 if (!session->closed)
301 prv = &session->next;
303 *prv = session->next;
305 session->notinset = 1;
306 if ( !session->refcount) {
307 session_destroy(session);
311 session_unlock(session);
318 * Initialize the session manager with a 'max_session_count',
319 * an initial common 'timeout' and an initial common token 'initok'.
321 * @param max_session_count maximum allowed session count in the same time
322 * @param timeout the initial default timeout of sessions
323 * @param initok the initial default token of sessions
326 int afb_session_init (int max_session_count, int timeout, const char *initok)
328 /* check parameters */
329 if (initok && strlen(initok) >= sizeof sessions.initok) {
330 ERROR("initial token '%s' too long (max length %d)",
331 initok, ((int)(sizeof sessions.initok)) - 1);
336 /* init the sessionset (after cleanup) */
338 sessionset_cleanup(1);
339 sessions.max = max_session_count;
340 sessions.timeout = timeout;
342 new_uuid(sessions.initok);
344 strcpy(sessions.initok, initok);
350 * Iterate the sessions and call 'callback' with
351 * the 'closure' for each session.
353 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
355 struct afb_session *session;
358 /* Loop on Sessions Table and remove anything that is older than timeout */
360 for (idx = 0 ; idx < HEADCOUNT; idx++) {
361 session = sessions.heads[idx];
363 if (!session->closed)
364 callback(closure, session);
365 session = session->next;
372 * Cleanup the sessionset of its closed or expired sessions
374 void afb_session_purge()
377 sessionset_cleanup(0);
382 * @return the initial token set at initialization
384 const char *afb_session_initial_token()
386 return sessions.initok;
389 /* Searchs the session of 'uuid' */
390 struct afb_session *afb_session_search (const char *uuid)
392 struct afb_session *session;
395 sessionset_cleanup(0);
396 session = sessionset_search(uuid, pearson4(uuid));
397 session = afb_session_addref(session);
404 * Creates a new session with 'timeout'
406 struct afb_session *afb_session_create (int timeout)
408 return afb_session_get(NULL, timeout, NULL);
412 * Returns the timeout of 'session' in seconds
414 int afb_session_timeout(struct afb_session *session)
418 /* compute timeout */
419 timeout = session->timeout;
420 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
421 timeout = sessions.timeout;
428 * Returns the second remaining before expiration of 'session'
430 int afb_session_what_remains(struct afb_session *session)
432 return (int)(session->expiration - NOW);
435 /* This function will return exiting session or newly created session */
436 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
438 char _uuid_[SIZEUUID];
440 struct afb_session *session;
446 now = sessionset_cleanup(0);
448 /* search for an existing one not too old */
450 hashidx = sessionset_make_uuid(_uuid_);
453 hashidx = pearson4(uuid);
454 session = sessionset_search(uuid, hashidx);
457 afb_session_addref(session);
462 /* create the session */
463 session = session_add(uuid, timeout, now, hashidx);
473 /* increase the use count on 'session' (can be NULL) */
474 struct afb_session *afb_session_addref(struct afb_session *session)
476 if (session != NULL) {
477 afb_hook_session_addref(session);
478 session_lock(session);
480 session_unlock(session);
485 /* decrease the use count of 'session' (can be NULL) */
486 void afb_session_unref(struct afb_session *session)
491 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_unlock(session);
542 && session->expiration >= NOW
543 && !(session->token[0] && strcmp (token, session->token));
544 session_unlock(session);
548 /* generate a new token and update client context */
549 void afb_session_new_token (struct afb_session *session)
551 session_unlock(session);
552 new_uuid(session->token);
553 session_update_expiration(session, NOW);
554 afb_hook_session_renew(session);
555 session_unlock(session);
558 /* Returns the uuid of 'session' */
559 const char *afb_session_uuid (struct afb_session *session)
561 return session->uuid;
564 /* Returns the token of 'session' */
565 const char *afb_session_token (struct afb_session *session)
567 return session->token;
571 * Get the index of the 'key' in the cookies array.
572 * @param key the key to scan
573 * @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;
583 * Set, get, replace, remove a cookie of 'key' for the 'session'
585 * The behaviour of this function depends on its parameters:
587 * @param session the session
588 * @param key the key of the cookie
589 * @param makecb the creation function or NULL
590 * @param freecb the release function or NULL
591 * @param closure an argument for makecb or the value if makecb==NULL
592 * @param replace a boolean enforcing replecement of the previous value
594 * @return the value of the cookie
596 * The 'key' is a pointer and compared as pointers.
598 * For getting the current value of the cookie:
600 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
602 * For storing the value of the cookie
604 * afb_session_cookie(session, key, NULL, NULL, value, 1)
606 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
610 struct cookie *cookie, **prv;
612 /* get key hashed index */
613 idx = cookeyidx(key);
615 /* lock session and search for the cookie of 'key' */
616 session_lock(session);
617 prv = &session->cookies[idx];
621 /* 'key' not found, create value using 'closure' and 'makecb' */
622 value = makecb ? makecb(closure) : closure;
623 /* store the the only if it has some meaning */
624 if (replace || makecb || freecb) {
625 cookie = malloc(sizeof *cookie);
628 /* calling freecb if there is no makecb may have issue */
629 if (makecb && freecb)
634 cookie->value = value;
635 cookie->freecb = freecb;
641 } else if (cookie->key == key) {
642 /* cookie of key found */
644 /* not replacing, get the value */
645 value = cookie->value;
647 /* create value using 'closure' and 'makecb' */
648 value = makecb ? makecb(closure) : closure;
650 /* free previous value is needed */
651 if (cookie->value != value && cookie->freecb)
652 cookie->freecb(cookie->value);
654 /* if both value and freecb are NULL drop the cookie */
655 if (!value && !freecb) {
659 /* store the value and its releaser */
660 cookie->value = value;
661 cookie->freecb = freecb;
666 prv = &(cookie->next);
670 /* unlock the session and return the value */
671 session_unlock(session);
676 * Get the cookie of 'key' in the 'session'.
678 * @param session the session to search in
679 * @param key the key of the data to retrieve
681 * @return the data staored for the key or NULL if the key isn't found
683 void *afb_session_get_cookie(struct afb_session *session, const void *key)
685 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
689 * Set the cookie of 'key' in the 'session' to the 'value' that can be
690 * cleaned using 'freecb' (if not null).
692 * @param session the session to set
693 * @param key the key of the data to store
694 * @param value the value to store at key
695 * @param freecb a function to use when the cookie value is to remove (or null)
697 * @return the data staored for the key or NULL if the key isn't found
700 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
702 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));