/*
- * Copyright (C) 2015-2018 "IoT.bzh"
+ * Copyright (C) 2015-2020 "IoT.bzh"
* Author "Fulup Ar Foll"
* Author: José Bollo <jose.bollo@iot.bzh>
*
#include <stdint.h>
#include <limits.h>
#include <string.h>
-#include <uuid/uuid.h>
#include <errno.h>
-#include <json-c/json.h>
-
#include "afb-session.h"
#include "afb-hook.h"
#include "verbose.h"
+#include "pearson.h"
+#include "uuid.h"
+
+#define SESSION_COUNT_MIN 5
+#define SESSION_COUNT_MAX 1000
-#define SIZEUUID 37
-#define HEADCOUNT 16
+/*
+ * Handling of cookies.
+ * Cookies are stored by session.
+ * The cookie count COOKIECOUNT must be a power of 2, possible values: 1, 2, 4, 8, 16, 32, 64, ...
+ * For low memory profile, small values are better, 1 is possible.
+ */
#define COOKIECOUNT 8
#define COOKIEMASK (COOKIECOUNT - 1)
#define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
#define NOW (time_now())
-/* structure for a cookie added to sessions */
+/**
+ * structure for a cookie added to sessions
+ */
struct cookie
{
- struct cookie *next; /* link to next cookie */
- const void *key; /* pointer key */
- void *value; /* value */
- void (*freecb)(void*); /* function to call when session is closed */
+ struct cookie *next; /**< link to next cookie */
+ const void *key; /**< pointer key */
+ void *value; /**< value */
+ void (*freecb)(void*); /**< function to call when session is closed */
};
-/*
+/**
* structure for session
*/
struct afb_session
{
- struct afb_session *next; /* link to the next */
- unsigned refcount; /* external reference count of the session */
- int timeout; /* timeout of the session */
- time_t expiration; /* expiration time of the token */
- pthread_mutex_t mutex; /* mutex of the session */
- struct cookie *cookies[COOKIECOUNT]; /* cookies of the session */
- uint8_t closed: 1; /* is the session closed ? */
- uint8_t autoclose: 1; /* close the session when unreferenced */
- uint8_t notinset: 1; /* session removed from the set of sessions */
- char uuid[SIZEUUID]; /* long term authentication of remote client */
- char token[SIZEUUID]; /* short term authentication of remote client */
+ struct afb_session *next; /**< link to the next */
+ uint16_t refcount; /**< count of reference to the session */
+ uint16_t id; /**< local id of the session */
+ int timeout; /**< timeout of the session */
+ time_t expiration; /**< expiration time of the session */
+ pthread_mutex_t mutex; /**< mutex of the session */
+ struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
+ char *lang; /**< current language setting for the session */
+ uint8_t closed: 1; /**< is the session closed ? */
+ uint8_t autoclose: 1; /**< close the session when unreferenced */
+ uint8_t notinset: 1; /**< session removed from the set of sessions */
+ uint8_t hash; /**< hash value of the uuid */
+ uuid_stringz_t uuid; /**< identification of client session */
};
-/* Session UUID are store in a simple array [for 10 sessions this should be enough] */
+/**
+ * structure for managing sessions
+ */
static struct {
- int count; /* current number of sessions */
- int max; /* maximum count of sessions */
- int timeout; /* common initial timeout */
- struct afb_session *heads[HEADCOUNT]; /* sessions */
- char initok[SIZEUUID]; /* common initial token */
- pthread_mutex_t mutex; /* declare a mutex to protect hash table */
+ uint16_t count; /**< current number of sessions */
+ uint16_t max; /**< maximum count of sessions */
+ uint16_t genid; /**< for generating ids */
+ int timeout; /**< common initial timeout */
+ struct afb_session *first; /**< sessions */
+ pthread_mutex_t mutex; /**< declare a mutex to protect hash table */
} sessions = {
.count = 0,
.max = 10,
+ .genid = 1,
.timeout = 3600,
- .heads = { 0 },
- .initok = { 0 },
+ .first = 0,
.mutex = PTHREAD_MUTEX_INITIALIZER
};
-/* Get the actual raw time */
+/**
+ * Get the actual raw time
+ */
static inline time_t time_now()
{
struct timespec ts;
return ts.tv_sec;
}
-/* generate a new fresh 'uuid' */
-static void new_uuid(char uuid[SIZEUUID])
-{
- uuid_t newuuid;
- uuid_generate(newuuid);
- uuid_unparse_lower(newuuid, uuid);
-}
-
-/*
- * Returns a tiny hash value for the 'text'.
- *
- * Tiny hash function inspired from pearson
- */
-static uint8_t pearson4(const char *text)
-{
- static uint8_t T[16] = {
- 4, 1, 6, 0, 9, 14, 11, 5,
- 2, 3, 12, 15, 10, 7, 8, 13
- };
- uint8_t r, c;
-
- for (r = 0; (c = (uint8_t)*text) ; text++) {
- r = T[r ^ (15 & c)];
- r = T[r ^ (c >> 4)];
- }
- return r; // % HEADCOUNT;
-}
-
/* lock the set of sessions for exclusive access */
static inline void sessionset_lock()
{
{
struct afb_session *session;
- session = sessions.heads[hashidx];
- while (session && strcmp(uuid, session->uuid))
+ session = sessions.first;
+ while (session && hashidx != session->hash && strcmp(uuid, session->uuid))
+ session = session->next;
+
+ return session;
+}
+
+/*
+ * search within the set of sessions the session of 'id'.
+ * return the session or NULL
+ */
+static struct afb_session *sessionset_search_id(uint16_t id)
+{
+ struct afb_session *session;
+
+ session = sessions.first;
+ while (session && id != session->id)
session = session->next;
return session;
static int sessionset_add(struct afb_session *session, uint8_t hashidx)
{
/* check availability */
- if (sessions.count >= sessions.max) {
+ if (sessions.max && sessions.count >= sessions.max) {
errno = EBUSY;
return -1;
}
/* add the session */
- session->next = sessions.heads[hashidx];
- sessions.heads[hashidx] = session;
+ session->next = sessions.first;
+ sessions.first = session;
sessions.count++;
return 0;
}
/* make a new uuid not used in the set of sessions */
-static uint8_t sessionset_make_uuid (char uuid[SIZEUUID])
+static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
{
uint8_t hashidx;
do {
- new_uuid(uuid);
+ uuid_new_stringz(uuid);
hashidx = pearson4(uuid);
} while(sessionset_search(uuid, hashidx));
return hashidx;
/* close it now */
session->closed = 1;
+#if WITH_AFB_HOOK
/* emit the hook */
afb_hook_session_close(session);
+#endif
/* release cookies */
for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
/* destroy the 'session' */
static void session_destroy (struct afb_session *session)
{
+#if WITH_AFB_HOOK
afb_hook_session_destroy(session);
+#endif
pthread_mutex_destroy(&session->mutex);
+ free(session->lang);
free(session);
}
pthread_mutex_init(&session->mutex, NULL);
session->refcount = 1;
strcpy(session->uuid, uuid);
- strcpy(session->token, sessions.initok);
session->timeout = timeout;
session_update_expiration(session, now);
+ session->id = ++sessions.genid;
+ while (session->id == 0 || sessionset_search_id(session->id) != NULL)
+ session->id = ++sessions.genid;
/* add */
if (sessionset_add(session, hashidx)) {
return NULL;
}
+#if WITH_AFB_HOOK
afb_hook_session_create(session);
+#endif
return session;
}
static time_t sessionset_cleanup (int force)
{
struct afb_session *session, **prv;
- int idx;
time_t now;
/* Loop on Sessions Table and remove anything that is older than timeout */
now = NOW;
- for (idx = 0 ; idx < HEADCOUNT; idx++) {
- prv = &sessions.heads[idx];
- while ((session = *prv)) {
- session_lock(session);
- if (force || session->expiration < now)
- session_close(session);
- if (!session->closed)
- prv = &session->next;
- else {
- *prv = session->next;
- sessions.count--;
- session->notinset = 1;
- if ( !session->refcount) {
- session_destroy(session);
- continue;
- }
- }
+ prv = &sessions.first;
+ while ((session = *prv)) {
+ session_lock(session);
+ if (force || session->expiration < now)
+ session_close(session);
+ if (!session->closed) {
+ prv = &session->next;
session_unlock(session);
+ } else {
+ *prv = session->next;
+ sessions.count--;
+ session->notinset = 1;
+ if (session->refcount)
+ session_unlock(session);
+ else
+ session_destroy(session);
}
}
return now;
/**
* Initialize the session manager with a 'max_session_count',
- * an initial common 'timeout' and an initial common token 'initok'.
+ * an initial common 'timeout'
*
* @param max_session_count maximum allowed session count in the same time
* @param timeout the initial default timeout of sessions
- * @param initok the initial default token of sessions
- *
*/
-int afb_session_init (int max_session_count, int timeout, const char *initok)
+int afb_session_init (int max_session_count, int timeout)
{
- /* check parameters */
- if (initok && strlen(initok) >= sizeof sessions.initok) {
- ERROR("initial token '%s' too long (max length %d)",
- initok, ((int)(sizeof sessions.initok)) - 1);
- errno = EINVAL;
- return -1;
- }
-
/* init the sessionset (after cleanup) */
sessionset_lock();
sessionset_cleanup(1);
- sessions.max = max_session_count;
- sessions.timeout = timeout;
- if (initok == NULL)
- new_uuid(sessions.initok);
+ if (max_session_count > SESSION_COUNT_MAX)
+ sessions.max = SESSION_COUNT_MAX;
+ else if (max_session_count < SESSION_COUNT_MIN)
+ sessions.max = SESSION_COUNT_MIN;
else
- strcpy(sessions.initok, initok);
+ sessions.max = (uint16_t)max_session_count;
+ sessions.timeout = timeout;
sessionset_unlock();
return 0;
}
void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
{
struct afb_session *session;
- int idx;
/* Loop on Sessions Table and remove anything that is older than timeout */
sessionset_lock();
- for (idx = 0 ; idx < HEADCOUNT; idx++) {
- session = sessions.heads[idx];
- while (session) {
- if (!session->closed)
- callback(closure, session);
- session = session->next;
- }
+ session = sessions.first;
+ while (session) {
+ if (!session->closed)
+ callback(closure, session);
+ session = session->next;
}
sessionset_unlock();
}
sessionset_unlock();
}
-/**
- * @return the initial token set at initialization
- */
-const char *afb_session_initial_token()
-{
- return sessions.initok;
-}
-
/* Searchs the session of 'uuid' */
struct afb_session *afb_session_search (const char *uuid)
{
*/
int afb_session_what_remains(struct afb_session *session)
{
- return (int)(session->expiration - NOW);
+ int diff = (int)(session->expiration - NOW);
+ return diff < 0 ? 0 : diff;
}
/* This function will return exiting session or newly created session */
struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
{
- char _uuid_[SIZEUUID];
+ uuid_stringz_t _uuid_;
uint8_t hashidx;
struct afb_session *session;
time_t now;
struct afb_session *afb_session_addref(struct afb_session *session)
{
if (session != NULL) {
+#if WITH_AFB_HOOK
afb_hook_session_addref(session);
+#endif
session_lock(session);
session->refcount++;
session_unlock(session);
if (session == NULL)
return;
+#if WITH_AFB_HOOK
afb_hook_session_unref(session);
+#endif
session_lock(session);
if (!--session->refcount) {
if (session->autoclose)
* Set the 'autoclose' flag of the 'session'
*
* A session whose autoclose flag is true will close as
- * soon as it is no more referenced.
+ * soon as it is no more referenced.
*
* @param session the session to set
* @param autoclose the value to set
return session->closed;
}
-/*
- * check whether the token of 'session' is 'token'
- * return 1 if true or 0 otherwise
- */
-int afb_session_check_token (struct afb_session *session, const char *token)
-{
- int r;
-
- session_unlock(session);
- r = !session->closed
- && session->expiration >= NOW
- && !(session->token[0] && strcmp (token, session->token));
- session_unlock(session);
- return r;
-}
-
-/* generate a new token and update client context */
-void afb_session_new_token (struct afb_session *session)
-{
- session_unlock(session);
- new_uuid(session->token);
- session_update_expiration(session, NOW);
- afb_hook_session_renew(session);
- session_unlock(session);
-}
-
/* Returns the uuid of 'session' */
const char *afb_session_uuid (struct afb_session *session)
{
return session->uuid;
}
-/* Returns the token of 'session' */
-const char *afb_session_token (struct afb_session *session)
+/* Returns the local id of 'session' */
+uint16_t afb_session_id (struct afb_session *session)
{
- return session->token;
+ return session->id;
}
/**
* @param key the key to scan
* @return the index of the list for key within cookies
*/
+#if COOKIEMASK
static int cookeyidx(const void *key)
{
intptr_t x = (intptr_t)key;
unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
return r & COOKIEMASK;
}
+#else
+# define cookeyidx(key) 0
+#endif
/**
* Set, get, replace, remove a cookie of 'key' for the 'session'
* @param makecb the creation function or NULL
* @param freecb the release function or NULL
* @param closure an argument for makecb or the value if makecb==NULL
- * @param replace a boolean enforcing replecement of the previous value
+ * @param replace a boolean enforcing replacement of the previous value
*
* @return the value of the cookie
*
* @param value the value to store at key
* @param freecb a function to use when the cookie value is to remove (or null)
*
- * @return the data staored for the key or NULL if the key isn't found
- *
+ * @return 0 in case of success or -1 in case of error
*/
int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
{
return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
}
+/**
+ * Set the language attached to the session
+ *
+ * @param session the session to set
+ * @param lang the language specifiction to set to session
+ *
+ * @return 0 in case of success or -1 in case of error
+ */
+int afb_session_set_language(struct afb_session *session, const char *lang)
+{
+ char *oldl, *newl;
+
+ newl = strdup(lang);
+ if (newl == NULL)
+ return -1;
+
+ oldl = session->lang;
+ session->lang = newl;
+ free(oldl);
+ return 0;
+}
+
+/**
+ * Get the language attached to the session
+ *
+ * @param session the session to query
+ * @param lang a default language specifiction
+ *
+ * @return the langauage specification to use for session
+ */
+const char *afb_session_get_language(struct afb_session *session, const char *lang)
+{
+ return session->lang ?: lang;
+}