From d45426257d5149c735e33e3055220625a919e7bc Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jos=C3=A9=20Bollo?= Date: Wed, 7 Feb 2018 17:44:20 +0100 Subject: [PATCH] afb-session: Refactor and test unit MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit The session are refactored and now to include a test unit. Change-Id: Ia8c4b707191f3af95c0549b333d14b384a81eaa7 Signed-off-by: José Bollo --- src/afb-session.c | 545 ++++++++++++++++++++++----------------- src/afb-session.h | 2 +- src/afb-stub-ws.c | 6 +- src/tests/CMakeLists.txt | 8 + src/tests/session/CMakeLists.txt | 23 ++ src/tests/session/test-session.c | 207 +++++++++++++++ 6 files changed, 549 insertions(+), 242 deletions(-) create mode 100644 src/tests/session/CMakeLists.txt create mode 100644 src/tests/session/test-session.c diff --git a/src/afb-session.c b/src/afb-session.c index f0cf0744..88d5b0d2 100644 --- a/src/afb-session.c +++ b/src/afb-session.c @@ -21,9 +21,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -41,39 +41,51 @@ #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)); diff --git a/src/afb-session.h b/src/afb-session.h index 9e3705b8..2fdd1a64 100644 --- a/src/afb-session.h +++ b/src/afb-session.h @@ -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); diff --git a/src/afb-stub-ws.c b/src/afb-stub-ws.c index 48ce9e0a..16475927 100644 --- a/src/afb-stub-ws.c +++ b/src/afb-stub-ws.c @@ -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 */ diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 774f59ae..7c052f32 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -17,3 +17,11 @@ ########################################################################### +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 index 00000000..91b99733 --- /dev/null +++ b/src/tests/session/CMakeLists.txt @@ -0,0 +1,23 @@ +########################################################################### +# Copyright 2017 IoT.bzh +# +# author: José Bollo +# +# 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 index 00000000..0189c3ad --- /dev/null +++ b/src/tests/session/test-session.c @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include + +#include + +#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(); +} -- 2.16.6