diff options
Diffstat (limited to 'src/afb-session.c')
-rw-r--r-- | src/afb-session.c | 536 |
1 files changed, 301 insertions, 235 deletions
diff --git a/src/afb-session.c b/src/afb-session.c index 61bce09a..6b6ad634 100644 --- a/src/afb-session.c +++ b/src/afb-session.c @@ -31,10 +31,15 @@ #include "afb-session.h" #include "verbose.h" -#define COOKEYCOUNT 8 -#define COOKEYMASK (COOKEYCOUNT - 1) +#define SIZEUUID 37 +#define HEADCOUNT 16 +#define COOKEYCOUNT 8 +#define COOKEYMASK (COOKEYCOUNT - 1) -#define NOW (time(NULL)) +#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)) struct cookie { @@ -46,40 +51,30 @@ struct cookie struct afb_session { + struct afb_session *next; /* link to the next */ unsigned refcount; int timeout; - time_t expiration; // expiration time of the token - time_t access; + time_t expiration; // expiration time of the token pthread_mutex_t mutex; - char uuid[37]; // long term authentication of remote client - char token[37]; // short term authentication of remote client struct cookie *cookies[COOKEYCOUNT]; + char autoclose; + char idx; + 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] static struct { - pthread_mutex_t mutex; // declare a mutex to protect hash table - struct afb_session **store; // sessions store - int count; // current number of sessions + 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[37]; + char initok[SIZEUUID]; } sessions; -/** - * Get the index of the 'key' in the cookies array. - * @param key the key to scan - * @return the index of the list for key within cookies - */ -static int cookeyidx(const void *key) -{ - intptr_t x = (intptr_t)key; - unsigned r = (unsigned)((x >> 5) ^ (x >> 15)); - return r & COOKEYMASK; -} - /* generate a uuid */ -static void new_uuid(char uuid[37]) +static void new_uuid(char uuid[SIZEUUID]) { uuid_t newuuid; uuid_generate(newuuid); @@ -97,40 +92,51 @@ static inline void unlock(struct afb_session *session) } // Free context [XXXX Should be protected again memory abort XXXX] -static void free_data (struct afb_session *session) +static void close_session(struct afb_session *session) { int idx; - struct cookie *cookie, *next; + struct cookie *cookie; - // free cookies + /* free cookies */ for (idx = 0 ; idx < COOKEYCOUNT ; idx++) { - cookie = session->cookies[idx]; - session->cookies[idx] = NULL; - while (cookie != NULL) { - next = cookie->next; + while ((cookie = session->cookies[idx])) { + session->cookies[idx] = cookie->next; if (cookie->freecb != NULL) cookie->freecb(cookie->value); free(cookie); - cookie = next; } } } +/* tiny hash function inspired from pearson */ +static int pearson4(const char *text) +{ + static uint8_t T[16] = { + 4, 1, 6, 0, 9, 14, 11, 5, + 2, 3, 12, 15, 10, 7, 8, 13 + }; + uint8_t r, c; + + for (r = 0; (c = (uint8_t)*text) ; text++) { + r = T[r ^ (15 & c)]; + r = T[r ^ (c >> 4)]; + } + return r; // % HEADCOUNT; +} + // Create a new store in RAM, not that is too small it will be automatically extended void afb_session_init (int max_session_count, int timeout, const char *initok) { - // let's create as store as hashtable does not have any - sessions.store = calloc (1 + (unsigned)max_session_count, sizeof *sessions.store); 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.store[0]->token)) + else if (strlen(initok) < sizeof sessions.initok) strcpy(sessions.initok, initok); else { - ERROR("initial token '%s' too long (max length 36)", initok); + ERROR("initial token '%s' too long (max length %d)", initok, ((int)(sizeof sessions.initok)) - 1); exit(1); } } @@ -140,196 +146,205 @@ const char *afb_session_initial_token() return sessions.initok; } -static struct afb_session *search (const char* uuid) +static struct afb_session *search (const char* uuid, int idx) { - int idx; struct afb_session *session; - assert (uuid != NULL); - - pthread_mutex_lock(&sessions.mutex); - - for (idx=0; idx < sessions.max; idx++) { - session = sessions.store[idx]; - if (session && (0 == strcmp (uuid, session->uuid))) - goto found; - } - session = NULL; + session = sessions.heads[idx]; + while (session && strcmp(uuid, session->uuid)) + session = session->next; -found: - pthread_mutex_unlock(&sessions.mutex); return session; } -static int destroy (struct afb_session *session) +static void destroy (struct afb_session *session) { - int idx; - int status; + struct afb_session **prv; assert (session != NULL); + close_session(session); pthread_mutex_lock(&sessions.mutex); - - for (idx=0; idx < sessions.max; idx++) { - if (sessions.store[idx] == session) { - sessions.store[idx] = NULL; + prv = &sessions.heads[(int)session->idx]; + while (*prv) + if (*prv != session) + prv = &((*prv)->next); + else { + *prv = session->next; sessions.count--; - status = 1; - goto deleted; + pthread_mutex_destroy(&session->mutex); + free(session); + break; } - } - status = 0; -deleted: pthread_mutex_unlock(&sessions.mutex); - return status; } -static int add (struct afb_session *session) +// Loop on every entry and remove old context sessions.hash +static time_t cleanup () { + struct afb_session *session, *next; int idx; - int status; - - assert (session != NULL); - - pthread_mutex_lock(&sessions.mutex); + time_t now; - for (idx=0; idx < sessions.max; idx++) { - if (NULL == sessions.store[idx]) { - sessions.store[idx] = session; - sessions.count++; - status = 1; - goto added; + // 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; } } - status = 0; -added: - pthread_mutex_unlock(&sessions.mutex); - return status; + return now; } -// Check if context timeout or not -static int is_expired (struct afb_session *ctx, time_t now) +static void update_timeout(struct afb_session *session, time_t now, int timeout) { - assert (ctx != NULL); - return ctx->expiration < now; + time_t expiration; + + /* compute expiration */ + if (timeout == AFB_SESSION_TIMEOUT_INFINITE) + 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 values */ + session->timeout = timeout; + session->expiration = expiration; } -// Check if context is active or not -static int is_active (struct afb_session *ctx, time_t now) +static void update_expiration(struct afb_session *session, time_t now) { - assert (ctx != NULL); - return ctx->uuid[0] != 0 && ctx->expiration >= now; + update_timeout(session, now, session->timeout); } -// Loop on every entry and remove old context sessions.hash -static void cleanup (time_t now) +static struct afb_session *add_session (const char *uuid, int timeout, time_t now, int idx) { - struct afb_session *ctx; - long idx; + struct afb_session *session; - // Loop on Sessions Table and remove anything that is older than timeout - for (idx=0; idx < sessions.max; idx++) { - ctx = sessions.store[idx]; - if (ctx != NULL && is_expired(ctx, now)) { - afb_session_close (ctx); - } + /* check arguments */ + if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout) + || (uuid && strlen(uuid) >= sizeof session->uuid)) { + errno = EINVAL; + return NULL; } -} -static struct afb_session *make_session (const char *uuid, int timeout, time_t now) -{ - struct afb_session *session; + /* check session count */ + if (sessions.count >= sessions.max) { + errno = EBUSY; + return NULL; + } /* allocates a new one */ session = calloc(1, sizeof *session); if (session == NULL) { errno = ENOMEM; - goto error; - } - pthread_mutex_init(&session->mutex, NULL); - - /* generate the uuid */ - if (uuid == NULL) { - new_uuid(session->uuid); - } else { - if (strlen(uuid) >= sizeof session->uuid) { - errno = EINVAL; - goto error2; - } - strcpy(session->uuid, uuid); + return NULL; } - /* init the token */ + /* initialize */ + pthread_mutex_init(&session->mutex, NULL); + session->refcount = 1; + strcpy(session->uuid, uuid); strcpy(session->token, sessions.initok); - session->timeout = timeout; - if (timeout != 0) - session->expiration = now + timeout; - else { - session->expiration = (time_t)(~(time_t)0); - if (session->expiration < 0) - session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1); - } - if (!add (session)) { - errno = ENOMEM; - goto error2; - } + update_timeout(session, now, timeout); + + /* link */ + session->idx = (char)idx; + session->next = sessions.heads[idx]; + sessions.heads[idx] = session; + sessions.count++; - session->access = now; - session->refcount = 1; return session; +} + +/* create a new session for the given timeout */ +static struct afb_session *new_session (int timeout, time_t now) +{ + int idx; + char uuid[SIZEUUID]; -error2: - free(session); -error: - return NULL; + do { + new_uuid(uuid); + idx = pearson4(uuid); + } while(search(uuid, idx)); + return add_session(uuid, timeout, now, idx); } -struct afb_session *afb_session_create (const char *uuid, int timeout) +/* Creates a new session with 'timeout' */ +struct afb_session *afb_session_create (int timeout) { time_t now; + struct afb_session *session; /* cleaning */ - now = NOW; - cleanup (now); + pthread_mutex_lock(&sessions.mutex); + now = cleanup(); + session = new_session(timeout, now); + pthread_mutex_unlock(&sessions.mutex); - /* search for an existing one not too old */ - if (uuid != NULL && search(uuid) != NULL) { - errno = EEXIST; - return NULL; - } + return session; +} + +/* Searchs the session of 'uuid' */ +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); + return session; - return make_session(uuid, timeout, now); } -// This function will return exiting session or newly created session -struct afb_session *afb_session_get (const char *uuid, int *created) +/* 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; struct afb_session *session; time_t now; /* cleaning */ - now = NOW; - cleanup (now); + pthread_mutex_lock(&sessions.mutex); + now = cleanup(); /* search for an existing one not too old */ - if (uuid != NULL) { - session = search(uuid); - if (!created) - return session; - if (session != NULL) { - *created = 0; - session->access = now; - session->refcount++; + if (!uuid) + session = new_session(timeout, now); + else { + idx = pearson4(uuid); + session = search(uuid, idx); + if (session) { + __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED); + pthread_mutex_unlock(&sessions.mutex); + if (created) + *created = 0; return session; } + session = add_session (uuid, timeout, now, idx); } + pthread_mutex_unlock(&sessions.mutex); if (created) - *created = 1; + *created = !!session; - return make_session(uuid, sessions.timeout, now); + return session; } +/* increase the use count on the session */ struct afb_session *afb_session_addref(struct afb_session *session) { if (session != NULL) @@ -337,32 +352,57 @@ struct afb_session *afb_session_addref(struct afb_session *session) return session; } +/* decrease the use count of the session */ void afb_session_unref(struct afb_session *session) { if (session != NULL) { assert(session->refcount != 0); if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) { - if (session->uuid[0] == 0) { + pthread_mutex_lock(&session->mutex); + if (session->autoclose || session->uuid[0] == 0) destroy (session); - pthread_mutex_destroy(&session->mutex); - free(session); - } + else + pthread_mutex_unlock(&session->mutex); } } } -// Free Client Session Context +// 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; - free_data (session); - if (session->refcount == 0) { + if (session->refcount) + close_session(session); + else { destroy (session); - free(session); + return; } } + pthread_mutex_unlock(&session->mutex); +} + +/* set the autoclose flag */ +void afb_session_set_autoclose(struct afb_session *session, int autoclose) +{ + assert(session != NULL); + session->autoclose = (char)!!autoclose; +} + +// is the session active? +int afb_session_is_active (struct afb_session *session) +{ + assert(session != NULL); + return !!session->uuid[0]; +} + +// is the session closed? +int afb_session_is_closed (struct afb_session *session) +{ + assert(session != NULL); + return !session->uuid[0]; } // Sample Generic Ping Debug API @@ -371,8 +411,10 @@ int afb_session_check_token (struct afb_session *session, const char *token) assert(session != NULL); assert(token != NULL); - // compare current token with previous one - if (!is_active (session, NOW)) + if (!session->uuid[0]) + return 0; + + if (session->expiration < NOW) return 0; if (session->token[0] && strcmp (token, session->token) != 0) @@ -390,111 +432,135 @@ void afb_session_new_token (struct afb_session *session) new_uuid(session->token); // keep track of time for session timeout and further clean up - if (session->timeout != 0) - session->expiration = NOW + session->timeout; + 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; } -static struct cookie *cookie_search(struct afb_session *session, const void *key, int *idx) -{ - struct cookie *cookie; - - cookie = session->cookies[*idx = cookeyidx(key)]; - while(cookie != NULL && cookie->key != key) - cookie = cookie->next; - return cookie; -} - -static struct cookie *cookie_add(struct afb_session *session, int idx, const void *key, void *value, void (*freecb)(void*)) +/** + * Get the index of the 'key' in the cookies array. + * @param key the key to scan + * @return the index of the list for key within cookies + */ +static int cookeyidx(const void *key) { - struct cookie *cookie; - - cookie = malloc(sizeof *cookie); - if (!cookie) - errno = ENOMEM; - else { - cookie->key = key; - cookie->value = value; - cookie->freecb = freecb; - cookie->next = session->cookies[idx]; - session->cookies[idx] = cookie; - } - return cookie; + intptr_t x = (intptr_t)key; + unsigned r = (unsigned)((x >> 5) ^ (x >> 15)); + return r & COOKEYMASK; } +/** + * Set, get, replace, remove a cookie of 'key' for the 'session' + * + * The behaviour of this function depends on its parameters: + * + * @param session the session + * @param key the key of the cookie + * @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 + * + * @return the value of the cookie + * + * The 'key' is a pointer and compared as pointers. + * + * For getting the current value of the cookie: + * + * afb_session_cookie(session, key, NULL, NULL, NULL, 0) + * + * For storing the value of the cookie + * + * afb_session_cookie(session, key, NULL, NULL, value, 1) + */ void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace) { int idx; void *value; - struct cookie *cookie; + struct cookie *cookie, **prv; + /* get key hashed index */ + idx = cookeyidx(key); + + /* lock session and search for the cookie of 'key' */ lock(session); - cookie = cookie_search(session, key, &idx); - if (cookie) { - if (!replace) - value = cookie->value; - else { + prv = &session->cookies[idx]; + for (;;) { + cookie = *prv; + if (!cookie) { + /* 'key' not found, create value using 'closure' and 'makecb' */ value = makecb ? makecb(closure) : closure; - if (cookie->value != value && cookie->freecb) - cookie->freecb(cookie->value); - cookie->value = value; - cookie->freecb = freecb; - } - } else { - value = makecb ? makecb(closure) : closure; - if (replace || makecb || freecb) { - cookie = cookie_add(session, idx, key, value, freecb); - if (!cookie) { - if (makecb && freecb) - freecb(value); - value = NULL; + /* store the the only if it has some meaning */ + if (replace || makecb || freecb) { + cookie = malloc(sizeof *cookie); + if (!cookie) { + errno = ENOMEM; + /* calling freecb if there is no makecb may have issue */ + if (makecb && freecb) + freecb(value); + value = NULL; + } else { + cookie->key = key; + cookie->value = value; + cookie->freecb = freecb; + cookie->next = NULL; + *prv = cookie; + } + } + break; + } else if (cookie->key == key) { + /* cookie of key found */ + if (!replace) + /* not replacing, get the value */ + value = cookie->value; + else { + /* create value using 'closure' and 'makecb' */ + value = makecb ? makecb(closure) : closure; + + /* free previous value is needed */ + 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 (!value && !freecb) { + *prv = cookie->next; + free(cookie); + } } + break; + } else { + prv = &(cookie->next); } } + + /* unlock the session and return the value */ unlock(session); return value; } void *afb_session_get_cookie(struct afb_session *session, const void *key) { - int idx; - void *value; - struct cookie *cookie; - - lock(session); - cookie = cookie_search(session, key, &idx); - value = cookie ? cookie->value : NULL; - unlock(session); - return value; + return afb_session_cookie(session, key, NULL, NULL, NULL, 0); } int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*)) { - int idx; - struct cookie *cookie; - - lock(session); - cookie = cookie_search(session, key, &idx); - if (!cookie) - cookie = cookie_add(session, idx, key, value, freecb); - else { - if (cookie->value != value && cookie->freecb) - cookie->freecb(cookie->value); - cookie->value = value; - cookie->freecb = freecb; - } - unlock(session); - return -!cookie; + return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1)); } |