/*
- * 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 <time.h>
#include <pthread.h>
#include <stdlib.h>
+#include <stdint.h>
+#include <limits.h>
#include <string.h>
-#include <uuid/uuid.h>
-#include <assert.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 COOKEYCOUNT 8
-#define COOKEYMASK (COOKEYCOUNT - 1)
+/*
+ * 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 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_now())
+/**
+ * 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
{
- unsigned refcount;
- int timeout;
- time_t expiration; // expiration time of the token
- time_t access;
- 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];
+ 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 {
- pthread_mutex_t mutex; // declare a mutex to protect hash table
- struct afb_session **store; // sessions store
- int count; // current number of sessions
- int max;
- int timeout;
- char initok[37];
-} sessions;
+ 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,
+ .first = 0,
+ .mutex = PTHREAD_MUTEX_INITIALIZER
+};
/**
- * 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
+ * Get the actual raw time
*/
-static int cookeyidx(const void *key)
+static inline time_t time_now()
{
- intptr_t x = (intptr_t)key;
- unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
- return r & COOKEYMASK;
+ struct timespec ts;
+ clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
+ return ts.tv_sec;
}
-/* generate a uuid */
-static void new_uuid(char uuid[37])
+/* lock the set of sessions for exclusive access */
+static inline void sessionset_lock()
{
- uuid_t newuuid;
- uuid_generate(newuuid);
- uuid_unparse_lower(newuuid, uuid);
+ pthread_mutex_lock(&sessions.mutex);
}
-static inline void lock(struct afb_session *session)
+/* unlock the set of sessions of exclusive access */
+static inline void sessionset_unlock()
{
- pthread_mutex_lock(&session->mutex);
+ pthread_mutex_unlock(&sessions.mutex);
}
-static inline void unlock(struct afb_session *session)
+/*
+ * 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)
{
- pthread_mutex_unlock(&session->mutex);
-}
+ struct afb_session *session;
-// Free context [XXXX Should be protected again memory abort XXXX]
-static void free_data (struct afb_session *session)
-{
- int idx;
- struct cookie *cookie, *next;
-
- // free cookies
- for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
- cookie = session->cookies[idx];
- session->cookies[idx] = NULL;
- while (cookie != NULL) {
- next = cookie->next;
- if (cookie->freecb != NULL)
- cookie->freecb(cookie->value);
- free(cookie);
- cookie = next;
- }
- }
-}
+ session = sessions.first;
+ while (session && hashidx != session->hash && strcmp(uuid, session->uuid))
+ session = session->next;
-// 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(struct afb_session));
- 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))
- strcpy(sessions.initok, initok);
- else {
- ERROR("initial token '%s' too long (max length 36)", initok);
- exit(1);
- }
+ return session;
}
-static struct afb_session *search (const char* uuid)
+/*
+ * 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)
{
- int idx;
struct afb_session *session;
- assert (uuid != NULL);
+ session = sessions.first;
+ while (session && id != session->id)
+ session = session->next;
- pthread_mutex_lock(&sessions.mutex);
+ return session;
+}
- for (idx=0; idx < sessions.max; idx++) {
- session = sessions.store[idx];
- if (session && (0 == strcmp (uuid, session->uuid)))
- goto found;
+/* add 'session' to the set of sessions */
+static int sessionset_add(struct afb_session *session, uint8_t hashidx)
+{
+ /* check availability */
+ if (sessions.max && sessions.count >= sessions.max) {
+ errno = EBUSY;
+ return -1;
}
- session = NULL;
-found:
- pthread_mutex_unlock(&sessions.mutex);
- return session;
+ /* add the session */
+ session->next = sessions.first;
+ sessions.first = session;
+ sessions.count++;
+ return 0;
}
-static int destroy (struct afb_session *session)
+/* make a new uuid not used in the set of sessions */
+static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
{
- int idx;
- int status;
+ uint8_t hashidx;
- assert (session != NULL);
+ do {
+ uuid_new_stringz(uuid);
+ hashidx = pearson4(uuid);
+ } while(sessionset_search(uuid, hashidx));
+ return hashidx;
+}
- pthread_mutex_lock(&sessions.mutex);
+/* lock the 'session' for exclusive access */
+static inline void session_lock(struct afb_session *session)
+{
+ pthread_mutex_lock(&session->mutex);
+}
- for (idx=0; idx < sessions.max; idx++) {
- if (sessions.store[idx] == session) {
- sessions.store[idx] = NULL;
- sessions.count--;
- status = 1;
- goto deleted;
- }
- }
- status = 0;
-deleted:
- pthread_mutex_unlock(&sessions.mutex);
- return status;
+/* unlock the 'session' of exclusive access */
+static inline void session_unlock(struct afb_session *session)
+{
+ pthread_mutex_unlock(&session->mutex);
}
-static int add (struct afb_session *session)
+/* close the 'session' */
+static void session_close(struct afb_session *session)
{
int idx;
- int status;
-
- assert (session != NULL);
-
- pthread_mutex_lock(&sessions.mutex);
+ struct cookie *cookie;
- for (idx=0; idx < sessions.max; idx++) {
- if (NULL == sessions.store[idx]) {
- sessions.store[idx] = session;
- sessions.count++;
- status = 1;
- goto added;
+ /* close only one time */
+ if (!session->closed) {
+ /* 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++) {
+ while ((cookie = session->cookies[idx])) {
+ session->cookies[idx] = cookie->next;
+ if (cookie->freecb != NULL)
+ cookie->freecb(cookie->value);
+ free(cookie);
+ }
}
}
- status = 0;
-added:
- pthread_mutex_unlock(&sessions.mutex);
- return status;
}
-// Check if context timeout or not
-static int is_expired (struct afb_session *ctx, time_t now)
+/* destroy the 'session' */
+static void session_destroy (struct afb_session *session)
{
- assert (ctx != NULL);
- return ctx->expiration < now;
+#if WITH_AFB_HOOK
+ afb_hook_session_destroy(session);
+#endif
+ pthread_mutex_destroy(&session->mutex);
+ free(session->lang);
+ free(session);
}
-// Check if context is active or not
-static int is_active (struct afb_session *ctx, time_t now)
+/* update expiration of 'session' according to 'now' */
+static void session_update_expiration(struct afb_session *session, time_t now)
{
- assert (ctx != NULL);
- return ctx->uuid[0] != 0 && ctx->expiration >= now;
-}
+ time_t expiration;
-// Loop on every entry and remove old context sessions.hash
-static void cleanup (time_t now)
-{
- struct afb_session *ctx;
- long idx;
+ /* compute expiration */
+ expiration = now + afb_session_timeout(session);
+ if (expiration < 0)
+ expiration = MAX_EXPIRATION;
- // 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);
- }
- }
+ /* record the expiration */
+ session->expiration = expiration;
}
-static struct afb_session *make_session (const char *uuid, int timeout, time_t now)
+/*
+ * 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;
+ /* check arguments */
+ if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
+ || (uuid && strlen(uuid) >= sizeof session->uuid)) {
+ errno = EINVAL;
+ return NULL;
+ }
+
/* allocates a new one */
session = calloc(1, sizeof *session);
if (session == NULL) {
errno = ENOMEM;
- goto error;
+ return NULL;
}
+
+ /* initialize */
pthread_mutex_init(&session->mutex, NULL);
+ session->refcount = 1;
+ strcpy(session->uuid, uuid);
+ 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)) {
+ free(session);
+ return NULL;
+ }
- /* generate the uuid */
- if (uuid == NULL) {
- new_uuid(session->uuid);
- } else {
- if (strlen(uuid) >= sizeof session->uuid) {
- errno = EINVAL;
- goto error2;
+#if WITH_AFB_HOOK
+ afb_hook_session_create(session);
+#endif
+
+ return session;
+}
+
+/* Remove expired sessions and return current time (now) */
+static time_t sessionset_cleanup (int force)
+{
+ struct afb_session *session, **prv;
+ time_t now;
+
+ /* Loop on Sessions Table and remove anything that is older than timeout */
+ now = NOW;
+ 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);
}
- strcpy(session->uuid, uuid);
}
+ return now;
+}
- /* init the token */
- 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;
+/**
+ * Initialize the session manager with a 'max_session_count',
+ * an initial common 'timeout'
+ *
+ * @param max_session_count maximum allowed session count in the same time
+ * @param timeout the initial default timeout of sessions
+ */
+int afb_session_init (int max_session_count, int timeout)
+{
+ /* init the sessionset (after cleanup) */
+ sessionset_lock();
+ sessionset_cleanup(1);
+ 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
+ sessions.max = (uint16_t)max_session_count;
+ sessions.timeout = timeout;
+ sessionset_unlock();
+ return 0;
+}
+
+/**
+ * Iterate the sessions and call 'callback' with
+ * the 'closure' for each session.
+ */
+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();
+ session = sessions.first;
+ while (session) {
+ if (!session->closed)
+ callback(closure, session);
+ session = session->next;
}
+ sessionset_unlock();
+}
- session->access = now;
- session->refcount = 1;
+/**
+ * Cleanup the sessionset of its closed or expired sessions
+ */
+void afb_session_purge()
+{
+ sessionset_lock();
+ sessionset_cleanup(0);
+ sessionset_unlock();
+}
+
+/* Searchs the session of 'uuid' */
+struct afb_session *afb_session_search (const char *uuid)
+{
+ struct afb_session *session;
+
+ sessionset_lock();
+ sessionset_cleanup(0);
+ session = sessionset_search(uuid, pearson4(uuid));
+ session = afb_session_addref(session);
+ sessionset_unlock();
return session;
-error2:
- free(session);
-error:
- return NULL;
}
-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;
+ return afb_session_get(NULL, timeout, NULL);
+}
- /* cleaning */
- now = NOW;
- cleanup (now);
+/**
+ * Returns the timeout of 'session' in seconds
+ */
+int afb_session_timeout(struct afb_session *session)
+{
+ int timeout;
- /* search for an existing one not too old */
- if (uuid != NULL && search(uuid) != NULL) {
- errno = EEXIST;
- return NULL;
- }
+ /* compute timeout */
+ timeout = session->timeout;
+ if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
+ timeout = sessions.timeout;
+ if (timeout < 0)
+ timeout = INT_MAX;
+ return timeout;
+}
- return make_session(uuid, timeout, now);
+/**
+ * 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 *created)
+/* This function will return exiting session or newly created session */
+struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
{
+ uuid_stringz_t _uuid_;
+ uint8_t hashidx;
struct afb_session *session;
time_t now;
+ int c;
/* cleaning */
- now = NOW;
- cleanup (now);
+ sessionset_lock();
+ now = sessionset_cleanup(0);
/* search for an existing one not too old */
- if (uuid != NULL) {
- session = search(uuid);
- if (session != NULL) {
- *created = 0;
- session->access = now;
- session->refcount++;
- return session;
+ if (!uuid) {
+ hashidx = sessionset_make_uuid(_uuid_);
+ uuid = _uuid_;
+ } else {
+ hashidx = pearson4(uuid);
+ session = sessionset_search(uuid, hashidx);
+ if (session) {
+ /* session found */
+ afb_session_addref(session);
+ c = 0;
+ goto end;
}
}
+ /* create the session */
+ session = session_add(uuid, timeout, now, hashidx);
+ c = 1;
+end:
+ sessionset_unlock();
+ if (created)
+ *created = c;
- *created = 1;
- return make_session(uuid, sessions.timeout, now);
+ return session;
}
+/* increase the use count on 'session' (can be NULL) */
struct afb_session *afb_session_addref(struct afb_session *session)
{
- if (session != NULL)
- __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
+ if (session != NULL) {
+#if WITH_AFB_HOOK
+ afb_hook_session_addref(session);
+#endif
+ session_lock(session);
+ session->refcount++;
+ session_unlock(session);
+ }
return 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)) {
- if (session->uuid[0] == 0) {
- destroy (session);
- pthread_mutex_destroy(&session->mutex);
- free(session);
- }
+ if (session == NULL)
+ return;
+
+#if WITH_AFB_HOOK
+ afb_hook_session_unref(session);
+#endif
+ session_lock(session);
+ if (!--session->refcount) {
+ if (session->autoclose)
+ session_close(session);
+ if (session->notinset) {
+ session_destroy(session);
+ return;
}
}
+ session_unlock(session);
}
-// Free Client Session Context
+/* close 'session' */
void afb_session_close (struct afb_session *session)
{
- assert(session != NULL);
- if (session->uuid[0] != 0) {
- session->uuid[0] = 0;
- free_data (session);
- if (session->refcount == 0) {
- destroy (session);
- free(session);
- }
- }
+ session_lock(session);
+ session_close(session);
+ session_unlock(session);
}
-// Sample Generic Ping Debug API
-int afb_session_check_token (struct afb_session *session, const char *token)
+/**
+ * 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);
- assert(token != NULL);
-
- // compare current token with previous one
- if (!is_active (session, NOW))
- return 0;
-
- if (session->token[0] && strcmp (token, session->token) != 0)
- return 0;
-
- return 1;
+ session->autoclose = !!autoclose;
}
-// generate a new token and update client context
-void afb_session_new_token (struct afb_session *session)
+/* is 'session' closed? */
+int afb_session_is_closed (struct afb_session *session)
{
- assert(session != NULL);
-
- // 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
- if (session->timeout != 0)
- session->expiration = NOW + session->timeout;
+ return session->closed;
}
+/* Returns the uuid of 'session' */
const char *afb_session_uuid (struct afb_session *session)
{
- assert(session != NULL);
return session->uuid;
}
-const char *afb_session_token (struct afb_session *session)
+/* Returns the local id of 'session' */
+uint16_t afb_session_id (struct afb_session *session)
{
- assert(session != NULL);
- return session->token;
+ return session->id;
}
-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
+ */
+#if COOKIEMASK
+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 & COOKIEMASK;
}
+#else
+# define cookeyidx(key) 0
+#endif
-void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void), void (*freecb)(void*))
+/**
+ * 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 replacement 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;
-
- lock(session);
- cookie = cookie_search(session, key, &idx);
- if (cookie)
- value = cookie->value;
- else {
- value = makecb ? makecb() : NULL;
- if (makecb || freecb) {
- cookie = cookie_add(session, idx, key, value, freecb);
- if (!cookie) {
- if (makecb && freecb)
- free(value);
- value = NULL;
+ struct cookie *cookie, **prv;
+
+ /* get key hashed index */
+ idx = cookeyidx(key);
+
+ /* lock session and search for the cookie of 'key' */
+ session_lock(session);
+ prv = &session->cookies[idx];
+ for (;;) {
+ cookie = *prv;
+ if (!cookie) {
+ /* 'key' not found, create value using 'closure' and 'makecb' */
+ value = makecb ? makecb(closure) : closure;
+ /* 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);
+
+ /* 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;
+ } else {
+ prv = &(cookie->next);
}
}
- unlock(session);
+
+ /* unlock the session and return the value */
+ 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)
{
- 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);
}
+/**
+ * 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 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*))
{
- int idx;
- struct cookie *cookie;
+ return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
+}
- 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;
+/**
+ * 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;
+}