/*
- * 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 "afb-session.h"
#include "afb-hook.h"
#include "verbose.h"
#include "pearson.h"
+#include "uuid.h"
-#define SIZEUUID 37
-#define HEADCOUNT 16
+#define SESSION_COUNT_MIN 5
+#define SESSION_COUNT_MAX 1000
+
+/*
+ * 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)
struct afb_session
{
struct afb_session *next; /**< link to the next */
- unsigned refcount; /**< count of reference to the session */
+ 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 token */
+ 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 */
- char uuid[SIZEUUID]; /**< long term authentication of remote client */
- char token[SIZEUUID]; /**< short term authentication of remote client */
+ uint8_t hash; /**< hash value of the uuid */
+ uuid_stringz_t uuid; /**< identification of client session */
};
/**
* 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
};
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);
-}
-
/* 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;
}
/* 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)
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
*
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;
+}