afb-session: Refactor and test unit
authorJosé Bollo <jose.bollo@iot.bzh>
Wed, 7 Feb 2018 16:44:20 +0000 (17:44 +0100)
committerJosé Bollo <jose.bollo@iot.bzh>
Fri, 9 Feb 2018 16:26:05 +0000 (17:26 +0100)
The session are refactored and now to include a test unit.

Change-Id: Ia8c4b707191f3af95c0549b333d14b384a81eaa7
Signed-off-by: José Bollo <jose.bollo@iot.bzh>
src/afb-session.c
src/afb-session.h
src/afb-stub-ws.c
src/tests/CMakeLists.txt
src/tests/session/CMakeLists.txt [new file with mode: 0644]
src/tests/session/test-session.c [new file with mode: 0644]

index f0cf074..88d5b0d 100644 (file)
@@ -21,9 +21,9 @@
 #include <time.h>
 #include <pthread.h>
 #include <stdlib.h>
+#include <stdint.h>
 #include <string.h>
 #include <uuid/uuid.h>
-#include <assert.h>
 #include <errno.h>
 
 #include <json-c/json.h>
 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
 #define NOW            (time(NULL))
 
+/* structure for a cookie added to sessions */
 struct cookie
 {
-       struct cookie *next;
-       const void *key;
-       void *value;
-       void (*freecb)(void*);
+       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;
-       int timeout;
-       time_t expiration;      // expiration time of the token
-       pthread_mutex_t mutex;
-       struct cookie *cookies[COOKIECOUNT];
-       char autoclose;
-       char idx;
-       char uuid[SIZEUUID];    // long term authentication of remote client
-       char token[SIZEUUID];   // short term authentication of remote client
+       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 */
 };
 
-// Session UUID are store in a simple array [for 10 sessions this should be enough]
+/* Session UUID are store in a simple array [for 10 sessions this should be enough] */
 static struct {
-       pthread_mutex_t mutex;  // declare a mutex to protect hash table
-       struct afb_session *heads[HEADCOUNT]; // sessions
-       int count;      // current number of sessions
-       int max;
-       int timeout;
-       char initok[SIZEUUID];
-} sessions;
+       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 */
+} sessions = {
+       .count = 0,
+       .max = 10,
+       .timeout = 3600,
+       .heads = { 0 },
+       .initok = { 0 },
+       .mutex = PTHREAD_MUTEX_INITIALIZER
+};
 
-/* generate a uuid */
+/* generate a new fresh 'uuid' */
 static void new_uuid(char uuid[SIZEUUID])
 {
        uuid_t newuuid;
@@ -81,35 +93,12 @@ static void new_uuid(char uuid[SIZEUUID])
        uuid_unparse_lower(newuuid, uuid);
 }
 
-static inline void lock(struct afb_session *session)
-{
-       pthread_mutex_lock(&session->mutex);
-}
-
-static inline void unlock(struct afb_session *session)
-{
-       pthread_mutex_unlock(&session->mutex);
-}
-
-// Free context [XXXX Should be protected again memory abort XXXX]
-static void close_session(struct afb_session *session)
-{
-       int idx;
-       struct cookie *cookie;
-
-       /* free cookies */
-       for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
-               while ((cookie = session->cookies[idx])) {
-                       session->cookies[idx] = cookie->next;
-                       if (cookie->freecb != NULL)
-                               cookie->freecb(cookie->value);
-                       free(cookie);
-               }
-       }
-}
-
-/* tiny hash function inspired from pearson */
-static int pearson4(const char *text)
+/*
+ * 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,
@@ -124,97 +113,111 @@ static int pearson4(const char *text)
        return r; // % HEADCOUNT;
 }
 
-/**
- * Initialize the session manager with a 'max_session_count',
- * an initial common 'timeout' and an initial common token 'initok'.
- *
- * @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)
+/* lock the set of sessions for exclusive access */
+static inline void sessionset_lock()
 {
-       pthread_mutex_init(&sessions.mutex, NULL);
-       sessions.max = max_session_count;
-       sessions.timeout = timeout;
-       if (initok == NULL)
-               /* without token, a secret is made to forbid creation of sessions */
-               new_uuid(sessions.initok);
-       else if (strlen(initok) < sizeof sessions.initok)
-               strcpy(sessions.initok, initok);
-       else {
-               ERROR("initial token '%s' too long (max length %d)", initok, ((int)(sizeof sessions.initok)) - 1);
-               errno = EINVAL;
-               return -1;
-       }
-       return 0;
+       pthread_mutex_lock(&sessions.mutex);
 }
 
-const char *afb_session_initial_token()
+/* unlock the set of sessions of exclusive access */
+static inline void sessionset_unlock()
 {
-       return sessions.initok;
+       pthread_mutex_unlock(&sessions.mutex);
 }
 
-static struct afb_session *search (const char* uuid, int idx)
+/*
+ * search within the set of sessions the session of 'uuid'.
+ * 'hashidx' is the precomputed hash for 'uuid'
+ * return the session or NULL
+ */
+static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
 {
        struct afb_session *session;
 
-       session = sessions.heads[idx];
+       session = sessions.heads[hashidx];
        while (session && strcmp(uuid, session->uuid))
                session = session->next;
 
        return session;
 }
 
-static void destroy (struct afb_session *session)
+/* add 'session' to the set of sessions */
+static int sessionset_add(struct afb_session *session, uint8_t hashidx)
 {
-       struct afb_session **prv;
+       /* check availability */
+       if (sessions.count >= sessions.max) {
+               errno = EBUSY;
+               return -1;
+       }
 
-       assert (session != NULL);
+       /* add the session */
+       session->next = sessions.heads[hashidx];
+       sessions.heads[hashidx] = session;
+       sessions.count++;
+       return 0;
+}
 
-       close_session(session);
-       pthread_mutex_lock(&sessions.mutex);
-       prv = &sessions.heads[(int)session->idx];
-       while (*prv)
-               if (*prv != session)
-                       prv = &((*prv)->next);
-               else {
-                       *prv = session->next;
-                       sessions.count--;
-                       pthread_mutex_destroy(&session->mutex);
-                       free(session);
-                       break;
-               }
-       pthread_mutex_unlock(&sessions.mutex);
+/* make a new uuid not used in the set of sessions */
+static uint8_t sessionset_make_uuid (char uuid[SIZEUUID])
+{
+       uint8_t hashidx;
+
+       do {
+               new_uuid(uuid);
+               hashidx = pearson4(uuid);
+       } while(sessionset_search(uuid, hashidx));
+       return hashidx;
+}
+
+/* lock the 'session' for exclusive access */
+static inline void session_lock(struct afb_session *session)
+{
+       pthread_mutex_lock(&session->mutex);
+}
+
+/* unlock the 'session' of exclusive access */
+static inline void session_unlock(struct afb_session *session)
+{
+       pthread_mutex_unlock(&session->mutex);
 }
 
-// Loop on every entry and remove old context sessions.hash
-static time_t cleanup ()
+/* close the 'session' */
+static void session_close(struct afb_session *session)
 {
-       struct afb_session *session, *next;
        int idx;
-       time_t now;
+       struct cookie *cookie;
 
-       // Loop on Sessions Table and remove anything that is older than timeout
-       now = NOW;
-       for (idx = 0 ; idx < HEADCOUNT; idx++) {
-               session = sessions.heads[idx];
-               while (session) {
-                       next = session->next;
-                       if (session->expiration < now)
-                               afb_session_close(session);
-                       session = next;
+       /* close only one time */
+       if (!session->closed) {
+               session->closed = 1;
+
+               /* free cookies */
+               for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
+                       while ((cookie = session->cookies[idx])) {
+                               session->cookies[idx] = cookie->next;
+                               if (cookie->freecb != NULL)
+                                       cookie->freecb(cookie->value);
+                               free(cookie);
+                       }
                }
        }
-       return now;
 }
 
-static void update_timeout(struct afb_session *session, time_t now, int timeout)
+/* destroy the 'session' */
+static void session_destroy (struct afb_session *session)
+{
+       pthread_mutex_destroy(&session->mutex);
+       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 = MAX_EXPIRATION;
        else {
@@ -226,17 +229,17 @@ static void update_timeout(struct afb_session *session, time_t now, int timeout)
                        expiration = MAX_EXPIRATION;
        }
 
-       /* record the values */
-       session->timeout = timeout;
+       /* record the expiration */
        session->expiration = expiration;
 }
 
-static void update_expiration(struct afb_session *session, time_t now)
-{
-       update_timeout(session, now, session->timeout);
-}
-
-static struct afb_session *add_session (const char *uuid, int timeout, time_t now, int idx)
+/*
+ * Add a new session with the 'uuid' (of 'hashidx')
+ * and the 'timeout' starting from 'now'.
+ * Add it to the set of sessions
+ * Return the created session
+ */
+static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
 {
        struct afb_session *session;
 
@@ -247,12 +250,6 @@ static struct afb_session *add_session (const char *uuid, int timeout, time_t no
                return NULL;
        }
 
-       /* check session count */
-       if (sessions.count >= sessions.max) {
-               errno = EBUSY;
-               return NULL;
-       }
-
        /* allocates a new one */
        session = calloc(1, sizeof *session);
        if (session == NULL) {
@@ -265,43 +262,98 @@ static struct afb_session *add_session (const char *uuid, int timeout, time_t no
        session->refcount = 1;
        strcpy(session->uuid, uuid);
        strcpy(session->token, sessions.initok);
-       update_timeout(session, now, timeout);
+       session->timeout = timeout;
+       session_update_expiration(session, now);
 
-       /* link */
-       session->idx = (char)idx;
-       session->next = sessions.heads[idx];
-       sessions.heads[idx] = session;
-       sessions.count++;
+       /* add */
+       if (sessionset_add(session, hashidx)) {
+               free(session);
+               return NULL;
+       }
 
        return session;
 }
 
-/* create a new session for the given timeout */
-static struct afb_session *new_session (int timeout, time_t now)
+/* Remove expired sessions and return current time (now) */
+static time_t sessionset_cleanup (int force)
 {
+       struct afb_session *session, **prv;
        int idx;
-       char uuid[SIZEUUID];
+       time_t now;
 
-       do {
-               new_uuid(uuid);
-               idx = pearson4(uuid);
-       } while(search(uuid, idx));
-       return add_session(uuid, timeout, now, idx);
+       /* 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;
+                               }
+                       }
+                       session_unlock(session);
+               }
+       }
+       return now;
 }
 
-/* Creates a new session with 'timeout' */
-struct afb_session *afb_session_create (int timeout)
+/**
+ * Initialize the session manager with a 'max_session_count',
+ * an initial common 'timeout' and an initial common token 'initok'.
+ *
+ * @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)
 {
-       time_t now;
-       struct afb_session *session;
+       /* 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;
+       }
 
-       /* cleaning */
-       pthread_mutex_lock(&sessions.mutex);
-       now = cleanup();
-       session = new_session(timeout, now);
-       pthread_mutex_unlock(&sessions.mutex);
+       /* 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);
+       else
+               strcpy(sessions.initok, initok);
+       sessionset_unlock();
+       return 0;
+}
 
-       return session;
+/**
+ * Cleanup the sessionset of its closed or expired sessions
+ */
+void afb_session_purge()
+{
+       sessionset_lock();
+       sessionset_cleanup(0);
+       sessionset_unlock();
+}
+
+/**
+ * @return the initial token set at initialization
+ */
+const char *afb_session_initial_token()
+{
+       return sessions.initok;
 }
 
 /* Searchs the session of 'uuid' */
@@ -309,52 +361,62 @@ struct afb_session *afb_session_search (const char *uuid)
 {
        struct afb_session *session;
 
-       /* cleaning */
-       pthread_mutex_lock(&sessions.mutex);
-       cleanup();
-       session = search(uuid, pearson4(uuid));
-       if (session)
-               __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
-       pthread_mutex_unlock(&sessions.mutex);
+       sessionset_lock();
+       sessionset_cleanup(0);
+       session = sessionset_search(uuid, pearson4(uuid));
+       session = afb_session_addref(session);
+       sessionset_unlock();
        return session;
 
 }
 
+/**
+ * Creates a new session with 'timeout'
+ */
+struct afb_session *afb_session_create (int timeout)
+{
+       return afb_session_get(NULL, timeout, NULL);
+}
+
 /* This function will return exiting session or newly created session */
 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
 {
-       int idx;
+       char _uuid_[SIZEUUID];
+       uint8_t hashidx;
        struct afb_session *session;
        time_t now;
+       int c;
 
        /* cleaning */
-       pthread_mutex_lock(&sessions.mutex);
-       now = cleanup();
+       sessionset_lock();
+       now = sessionset_cleanup(0);
 
        /* search for an existing one not too old */
-       if (!uuid)
-               session = new_session(timeout, now);
-       else {
-               idx = pearson4(uuid);
-               session = search(uuid, idx);
+       if (!uuid) {
+               hashidx = sessionset_make_uuid(_uuid_);
+               uuid = _uuid_;
+       } else {
+               hashidx = pearson4(uuid);
+               session = sessionset_search(uuid, hashidx);
                if (session) {
-                       __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
-                       pthread_mutex_unlock(&sessions.mutex);
-                       if (created)
-                               *created = 0;
-                       return session;
+                       /* session found */
+                       afb_session_addref(session);
+                       c = 0;
+                       goto end;
                }
-               session = add_session (uuid, timeout, now, idx);
        }
-       pthread_mutex_unlock(&sessions.mutex);
-
+       /* create the session */
+       session = session_add(uuid, timeout, now, hashidx);
+       c = 1;
+end:
+       sessionset_unlock();
        if (created)
-               *created = !!session;
+               *created = c;
 
        return session;
 }
 
-/* increase the use count on the session */
+/* increase the use count on 'session' (can be NULL) */
 struct afb_session *afb_session_addref(struct afb_session *session)
 {
        if (session != NULL)
@@ -362,100 +424,87 @@ struct afb_session *afb_session_addref(struct afb_session *session)
        return session;
 }
 
-/* decrease the use count of the session */
+/* decrease the use count of 'session' (can be NULL) */
 void afb_session_unref(struct afb_session *session)
 {
-       if (session != NULL) {
-               assert(session->refcount != 0);
-               if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
-                       pthread_mutex_lock(&session->mutex);
-                       if (session->autoclose || session->uuid[0] == 0)
-                               destroy (session);
-                       else
-                               pthread_mutex_unlock(&session->mutex);
-               }
-       }
-}
+       if (session == NULL)
+               return;
 
-// close Client Session Context
-void afb_session_close (struct afb_session *session)
-{
-       assert(session != NULL);
-       pthread_mutex_lock(&session->mutex);
-       if (session->uuid[0] != 0) {
-               session->uuid[0] = 0;
-               if (session->refcount)
-                       close_session(session);
-               else {
-                       destroy (session);
+       session_lock(session);
+       if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
+               if (session->autoclose)
+                       session_close(session);
+               if (session->notinset) {
+                       session_destroy(session);
                        return;
                }
        }
-       pthread_mutex_unlock(&session->mutex);
+       session_unlock(session);
 }
 
-/* set the autoclose flag */
-void afb_session_set_autoclose(struct afb_session *session, int autoclose)
+/* close 'session' */
+void afb_session_close (struct afb_session *session)
 {
-       assert(session != NULL);
-       session->autoclose = (char)!!autoclose;
+       session_lock(session);
+       session_close(session);
+       session_unlock(session);
 }
 
-// is the session active?
-int afb_session_is_active (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. 
+ *
+ * @param session    the session to set
+ * @param autoclose  the value to set
+ */
+void afb_session_set_autoclose(struct afb_session *session, int autoclose)
 {
-       assert(session != NULL);
-       return !!session->uuid[0];
+       session->autoclose = !!autoclose;
 }
 
-// is the session closed?
+/* is 'session' closed? */
 int afb_session_is_closed (struct afb_session *session)
 {
-       assert(session != NULL);
-       return !session->uuid[0];
+       return session->closed;
 }
 
-// Sample Generic Ping Debug API
+/*
+ * 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)
 {
-       assert(session != NULL);
-       assert(token != NULL);
-
-       if (!session->uuid[0])
-               return 0;
-
-       if (session->expiration < NOW)
-               return 0;
+       int r;
 
-       if (session->token[0] && strcmp (token, session->token) != 0)
-               return 0;
-
-       return 1;
+       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
+/* generate a new token and update client context */
 void afb_session_new_token (struct afb_session *session)
 {
-       assert(session != NULL);
-
-       // Old token was valid let's regenerate a new one
+       /* Old token was valid let's regenerate a new one */
        new_uuid(session->token);
 
-       // keep track of time for session timeout and further clean up
-       update_expiration(session, NOW);
+       /* keep track of time for session timeout and further clean up */
+       session_update_expiration(session, NOW);
 }
 
 /* Returns the uuid of 'session' */
 const char *afb_session_uuid (struct afb_session *session)
 {
-       assert(session != NULL);
        return session->uuid;
 }
 
 /* Returns the token of 'session' */
 const char *afb_session_token (struct afb_session *session)
 {
-       assert(session != NULL);
        return session->token;
 }
 
@@ -505,7 +554,7 @@ void *afb_session_cookie(struct afb_session *session, const void *key, void *(*m
        idx = cookeyidx(key);
 
        /* lock session and search for the cookie of 'key' */
-       lock(session);
+       session_lock(session);
        prv = &session->cookies[idx];
        for (;;) {
                cookie = *prv;
@@ -543,14 +592,14 @@ void *afb_session_cookie(struct afb_session *session, const void *key, void *(*m
                                if (cookie->value != value && cookie->freecb)
                                        cookie->freecb(cookie->value);
 
-                               /* store the value and its releaser */
-                               cookie->value = value;
-                               cookie->freecb = freecb;
-
-                               /* but if both are NULL drop the cookie */
+                               /* if both value and freecb are NULL drop the cookie */
                                if (!value && !freecb) {
                                        *prv = cookie->next;
                                        free(cookie);
+                               } else {
+                                       /* store the value and its releaser */
+                                       cookie->value = value;
+                                       cookie->freecb = freecb;
                                }
                        }
                        break;
@@ -560,15 +609,35 @@ void *afb_session_cookie(struct afb_session *session, const void *key, void *(*m
        }
 
        /* unlock the session and return the value */
-       unlock(session);
+       session_unlock(session);
        return value;
 }
 
+/**
+ * Get the cookie of 'key' in the 'session'.
+ *
+ * @param session  the session to search in
+ * @param key      the key of the data to retrieve
+ *
+ * @return the data staored for the key or NULL if the key isn't found
+ */
 void *afb_session_get_cookie(struct afb_session *session, const void *key)
 {
        return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
 }
 
+/**
+ * Set the cookie of 'key' in the 'session' to the 'value' that can be
+ * cleaned using 'freecb' (if not null).
+ *
+ * @param session  the session to set
+ * @param key      the key of the data to store
+ * @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
+ * 
+ */
 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));
index 9e3705b..2fdd1a6 100644 (file)
@@ -24,6 +24,7 @@ struct afb_session;
 #define AFB_SESSION_TIMEOUT_IS_VALID(x) ((x) >= AFB_SESSION_TIMEOUT_DEFAULT)
 
 extern int afb_session_init(int max_session_count, int timeout, const char *initok);
+extern void afb_session_purge();
 extern const char *afb_session_initial_token();
 
 extern struct afb_session *afb_session_create (int timeout);
@@ -36,7 +37,6 @@ extern void afb_session_unref(struct afb_session *session);
 extern void afb_session_set_autoclose(struct afb_session *session, int autoclose);
 
 extern void afb_session_close(struct afb_session *session);
-extern int afb_session_is_active (struct afb_session *session);
 extern int afb_session_is_closed (struct afb_session *session);
 
 extern int afb_session_check_token(struct afb_session *session, const char *token);
index 48ce9e0..1647592 100644 (file)
@@ -481,13 +481,13 @@ static void record_session(struct afb_stub_ws *stubws, struct afb_session *sessi
        while ((s = *prv)) {
                if (s->session == session)
                        return;
-               if (afb_session_is_active(s->session))
-                       prv = &s->next;
-               else {
+               if (afb_session_is_closed(s->session)) {
                        *prv = s->next;
                        afb_session_unref(s->session);
                        free(s);
                }
+               else
+                       prv = &s->next;
        }
 
        /* create */
index 774f59a..7c052f3 100644 (file)
 ###########################################################################
 
 
+PKG_CHECK_MODULES(check check)
+if(check_FOUND)
+       INCLUDE_DIRECTORIES(${INCLUDE_DIRS} ${check_INCLUDE_DIRS})
+       SET(link_libraries ${link_libraries} ${check_LDFLAGS})
+       add_subdirectory(session)
+else(check_FOUND)
+       MESSAGE(WARNING "check not found! no test!")
+endif(check_FOUND)
diff --git a/src/tests/session/CMakeLists.txt b/src/tests/session/CMakeLists.txt
new file mode 100644 (file)
index 0000000..91b9973
--- /dev/null
@@ -0,0 +1,23 @@
+###########################################################################
+# Copyright 2017 IoT.bzh
+#
+# author: José Bollo <jose.bollo@iot.bzh>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###########################################################################
+
+add_executable(test-session test-session.c)
+target_include_directories(test-session PRIVATE ../..)
+target_link_libraries(test-session afb-lib ${link_libraries})
+add_test(NAME session COMMAND test-session)
+
diff --git a/src/tests/session/test-session.c b/src/tests/session/test-session.c
new file mode 100644 (file)
index 0000000..0189c3a
--- /dev/null
@@ -0,0 +1,207 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <string.h>
+
+#include <check.h>
+
+#include "afb-session.h"
+
+#define GOOD_UUID  "123456789012345678901234567890123456"
+#define BAD_UUID   "1234567890123456789012345678901234567"
+
+START_TEST (test_initialisation)
+{
+       ck_assert_int_eq(0, afb_session_init(0, 0, NULL));
+       ck_assert_int_eq(0, afb_session_init(200, 0, NULL));
+       ck_assert_int_eq(0, afb_session_init(10, 0, GOOD_UUID));
+       ck_assert_str_eq(GOOD_UUID, afb_session_initial_token());
+       ck_assert_int_eq(-1, afb_session_init(10, 0, BAD_UUID));
+       ck_assert_int_eq(errno, EINVAL);
+}
+END_TEST
+
+
+START_TEST (test_sanity)
+{
+       struct afb_session *s;
+       s = afb_session_addref(NULL);
+       ck_assert(!s);
+       afb_session_unref(NULL);
+       ck_assert(1);
+}
+END_TEST
+
+
+START_TEST (test_creation)
+{
+       char *uuid;
+       struct afb_session *s, *x;
+
+       /* init */
+       ck_assert_int_eq(0, afb_session_init(10, 3600, GOOD_UUID));
+
+       /* create a session */
+       s = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(s);
+
+       /* the session is valid */
+       ck_assert(afb_session_uuid(s) != NULL);
+       ck_assert(afb_session_token(s) != NULL);
+       ck_assert(!afb_session_is_closed(s));
+       
+       /* token is the initial one */
+       ck_assert_str_eq(afb_session_token(s), GOOD_UUID);
+       ck_assert(afb_session_check_token(s, GOOD_UUID));
+       ck_assert(afb_session_check_token(s, afb_session_token(s)));
+
+       /* token can be renewed */
+       afb_session_new_token(s);
+       ck_assert(strcmp(afb_session_token(s), GOOD_UUID));
+       ck_assert(!afb_session_check_token(s, GOOD_UUID));
+       ck_assert(afb_session_check_token(s, afb_session_token(s)));
+
+       /* query the session */
+       uuid = strdup(afb_session_uuid(s));
+       x = afb_session_search(uuid);
+       ck_assert(x == s);
+
+       /* still alive after search */
+       afb_session_unref(x);
+       afb_session_unref(s);
+       s = afb_session_search(uuid);
+       ck_assert(s);
+       ck_assert(x == s);
+
+       /* but not after closing */
+       afb_session_close(s);
+       ck_assert(afb_session_is_closed(s));
+       afb_session_unref(s);
+       afb_session_purge();
+       s = afb_session_search(uuid);
+       ck_assert(!s);
+       free(uuid);
+}
+END_TEST
+
+
+START_TEST (test_capacity)
+{
+       struct afb_session *s[3];
+       ck_assert_int_eq(0, afb_session_init(2, 3600, GOOD_UUID));
+       s[0] = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(s[0]);
+       s[1] = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(s[1]);
+       s[2] = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(!s[2]);
+       afb_session_close(s[0]);
+       afb_session_unref(s[0]);
+       s[2] = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(s[2]);
+       s[0] = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(!s[0]);
+       afb_session_unref(s[0]);
+       afb_session_unref(s[1]);
+       afb_session_unref(s[2]);
+}
+END_TEST
+
+
+void *mkcookie_got;
+void *mkcookie(void *closure)
+{
+       mkcookie_got = closure;
+       return closure;
+}
+
+void *freecookie_got;
+void freecookie(void *item)
+{
+       freecookie_got = item;
+}
+
+START_TEST (test_cookies)
+{
+       char *k[] = { "key1", "key2", "key3", NULL }, *p, *q, *d = "default";
+       struct afb_session *s;
+       int i, j;
+
+       /* init */
+       ck_assert_int_eq(0, afb_session_init(10, 3600, GOOD_UUID));
+
+extern void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace);
+
+       /* create a session */
+       s = afb_session_create(AFB_SESSION_TIMEOUT_DEFAULT);
+       ck_assert(s);
+
+       /* set the cookie */
+       for (i = 0 ; k[i] ; i++) {
+               for (j = 0 ; k[j] ; j++) {
+                       /* retrieve the previous value */
+                       mkcookie_got = freecookie_got = NULL;
+                       p = afb_session_cookie(s, k[j], NULL, NULL, NULL, 0);
+                       if (!p) {
+                               /* never set (i = 0) */
+                               q = afb_session_cookie(s, k[j], NULL, NULL, d, 0);
+                               ck_assert(q == d);
+                               p = afb_session_cookie(s, k[j], NULL, NULL, NULL, 0);
+                               ck_assert(!p);
+                       }
+                       q = afb_session_cookie(s, k[j], mkcookie, freecookie, k[i], 1);
+                       ck_assert(q == k[i]);
+                       ck_assert(mkcookie_got == q);
+                       ck_assert(freecookie_got == p);
+               }
+       }
+
+       /* drop cookies */
+       for (i = 1 ; k[i] ; i++) {
+               mkcookie_got = freecookie_got = NULL;
+               p = afb_session_cookie(s, k[i], NULL, NULL, NULL, 0);
+               ck_assert(!freecookie_got);
+               q = afb_session_cookie(s, k[i], NULL, NULL, NULL, 1);
+               ck_assert(!q);
+               ck_assert(freecookie_got == p);
+       }
+
+       /* closing session */
+       p = afb_session_cookie(s, k[0], NULL, NULL, NULL, 0);
+       mkcookie_got = freecookie_got = NULL;
+       afb_session_close(s);
+       ck_assert(freecookie_got == p);
+       p = afb_session_cookie(s, k[0], NULL, NULL, NULL, 0);
+       ck_assert(!p);
+       afb_session_unref(s);
+}
+END_TEST
+
+static Suite *suite;
+static TCase *tcase;
+
+void mksuite(const char *name) { suite = suite_create(name); }
+void addtcase(const char *name) { tcase = tcase_create(name); suite_add_tcase(suite, tcase); }
+void addtest(TFun fun) { tcase_add_test(tcase, fun); }
+int srun()
+{
+       int nerr;
+       SRunner *srunner = srunner_create(suite);
+       srunner_run_all(srunner, CK_NORMAL);
+       nerr = srunner_ntests_failed(srunner);
+       srunner_free(srunner);
+       return nerr;
+}
+
+int main(int ac, char **av)
+{
+       mksuite("session");
+               addtcase("session");
+                       addtest(test_initialisation);
+                       addtest(test_sanity);
+                       addtest(test_creation);
+                       addtest(test_capacity);
+                       addtest(test_cookies);
+       return !!srun();
+}