Update copyright dates
[src/app-framework-binder.git] / src / afb-session.c
index c23d414..80bad1b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
+ * Copyright (C) 2015-2020 "IoT.bzh"
  * Author "Fulup Ar Foll"
  * Author: José Bollo <jose.bollo@iot.bzh>
  *
 #include <pthread.h>
 #include <stdlib.h>
 #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 _MAXEXP_       ((time_t)(~(time_t)0))
 #define _MAXEXP2_      ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
-#define NOW            (time(NULL))
+#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
 };
 
-/* 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
+/**
+ * Get the actual raw time
  */
-static uint8_t pearson4(const char *text)
+static inline time_t time_now()
 {
-       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;
+       struct timespec ts;
+       clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+       return ts.tv_sec;
 }
 
 /* lock the set of sessions for exclusive access */
@@ -135,8 +130,23 @@ static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
 {
        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;
@@ -146,25 +156,25 @@ static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
 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;
@@ -193,8 +203,10 @@ static void session_close(struct afb_session *session)
                /* 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++) {
@@ -211,29 +223,23 @@ static void session_close(struct afb_session *session)
 /* 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);
 }
 
 /* update expiration of 'session' according to 'now' */
 static void session_update_expiration(struct afb_session *session, time_t now)
 {
-       int timeout;
        time_t expiration;
 
        /* compute expiration */
-       timeout = session->timeout;
-       if (timeout == AFB_SESSION_TIMEOUT_INFINITE)
+       expiration = now + afb_session_timeout(session);
+       if (expiration < 0)
                expiration = MAX_EXPIRATION;
-       else {
-               if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
-                       expiration = now + sessions.timeout;
-               else
-                       expiration = now + timeout;
-               if (expiration < 0)
-                       expiration = MAX_EXPIRATION;
-       }
 
        /* record the expiration */
        session->expiration = expiration;
@@ -267,9 +273,11 @@ static struct afb_session *session_add(const char *uuid, int timeout, time_t now
        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)) {
@@ -277,7 +285,9 @@ static struct afb_session *session_add(const char *uuid, int timeout, time_t now
                return NULL;
        }
 
+#if WITH_AFB_HOOK
        afb_hook_session_create(session);
+#endif
 
        return session;
 }
@@ -286,29 +296,26 @@ static struct afb_session *session_add(const char *uuid, int timeout, time_t now
 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;
@@ -316,52 +323,54 @@ static time_t sessionset_cleanup (int force)
 
 /**
  * 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;
 }
 
 /**
- * Cleanup the sessionset of its closed or expired sessions
+ * Iterate the sessions and call 'callback' with
+ * the 'closure' for each session.
  */
-void afb_session_purge()
+void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
 {
+       struct afb_session *session;
+
+       /* Loop on Sessions Table and remove anything that is older than timeout */
        sessionset_lock();
-       sessionset_cleanup(0);
+       session = sessions.first;
+       while (session) {
+               if (!session->closed)
+                       callback(closure, session);
+               session = session->next;
+       }
        sessionset_unlock();
 }
 
 /**
- * @return the initial token set at initialization
+ * Cleanup the sessionset of its closed or expired sessions
  */
-const char *afb_session_initial_token()
+void afb_session_purge()
 {
-       return sessions.initok;
+       sessionset_lock();
+       sessionset_cleanup(0);
+       sessionset_unlock();
 }
 
 /* Searchs the session of 'uuid' */
@@ -386,10 +395,35 @@ struct afb_session *afb_session_create (int timeout)
        return afb_session_get(NULL, timeout, NULL);
 }
 
+/**
+ * Returns the timeout of 'session' in seconds
+ */
+int afb_session_timeout(struct afb_session *session)
+{
+       int timeout;
+
+       /* compute timeout */
+       timeout = session->timeout;
+       if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
+               timeout = sessions.timeout;
+       if (timeout < 0)
+               timeout = INT_MAX;
+       return timeout;
+}
+
+/**
+ * Returns the second remaining before expiration of 'session'
+ */
+int afb_session_what_remains(struct afb_session *session)
+{
+       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;
@@ -428,7 +462,10 @@ end:
 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);
        }
@@ -441,9 +478,11 @@ void afb_session_unref(struct afb_session *session)
        if (session == NULL)
                return;
 
-       session_lock(session);
+#if WITH_AFB_HOOK
        afb_hook_session_unref(session);
-       if (--session->refcount) {
+#endif
+       session_lock(session);
+       if (!--session->refcount) {
                if (session->autoclose)
                        session_close(session);
                if (session->notinset) {
@@ -466,7 +505,7 @@ void afb_session_close (struct afb_session *session)
  * 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
@@ -482,42 +521,16 @@ int afb_session_is_closed (struct afb_session *session)
        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;
 }
 
 /**
@@ -525,12 +538,16 @@ const char *afb_session_token (struct afb_session *session)
  * @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'
@@ -542,7 +559,7 @@ static int cookeyidx(const void *key)
  * @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
  *
@@ -647,11 +664,44 @@ void *afb_session_get_cookie(struct afb_session *session, const void *key)
  * @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;
+}