2 * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
3 * Author "Fulup Ar Foll"
4 * Author: José Bollo <jose.bollo@iot.bzh>
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
25 #include <uuid/uuid.h>
29 #include <json-c/json.h>
31 #include "afb-session.h"
37 #define COOKEYMASK (COOKEYCOUNT - 1)
39 #define _MAXEXP_ ((time_t)(~(time_t)0))
40 #define _MAXEXP2_ ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
41 #define MAX_EXPIRATION (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
42 #define NOW (time(NULL))
49 void (*freecb)(void*);
54 struct afb_session *next; /* link to the next */
57 time_t expiration; // expiration time of the token
58 pthread_mutex_t mutex;
59 struct cookie *cookies[COOKEYCOUNT];
62 char uuid[SIZEUUID]; // long term authentication of remote client
63 char token[SIZEUUID]; // short term authentication of remote client
66 // Session UUID are store in a simple array [for 10 sessions this should be enough]
68 pthread_mutex_t mutex; // declare a mutex to protect hash table
69 struct afb_session *heads[HEADCOUNT]; // sessions
70 int count; // current number of sessions
73 char initok[SIZEUUID];
77 static void new_uuid(char uuid[SIZEUUID])
80 uuid_generate(newuuid);
81 uuid_unparse_lower(newuuid, uuid);
84 static inline void lock(struct afb_session *session)
86 pthread_mutex_lock(&session->mutex);
89 static inline void unlock(struct afb_session *session)
91 pthread_mutex_unlock(&session->mutex);
94 // Free context [XXXX Should be protected again memory abort XXXX]
95 static void close_session(struct afb_session *session)
98 struct cookie *cookie;
101 for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
102 while ((cookie = session->cookies[idx])) {
103 session->cookies[idx] = cookie->next;
104 if (cookie->freecb != NULL)
105 cookie->freecb(cookie->value);
111 /* tiny hash function inspired from pearson */
112 static int pearson4(const char *text)
114 static uint8_t T[16] = {
115 4, 1, 6, 0, 9, 14, 11, 5,
116 2, 3, 12, 15, 10, 7, 8, 13
120 for (r = 0; (c = (uint8_t)*text) ; text++) {
124 return r; // % HEADCOUNT;
127 // Create a new store in RAM, not that is too small it will be automatically extended
128 void afb_session_init (int max_session_count, int timeout, const char *initok)
130 pthread_mutex_init(&sessions.mutex, NULL);
131 sessions.max = max_session_count;
132 sessions.timeout = timeout;
134 /* without token, a secret is made to forbid creation of sessions */
135 new_uuid(sessions.initok);
136 else if (strlen(initok) < sizeof sessions.initok)
137 strcpy(sessions.initok, initok);
139 ERROR("initial token '%s' too long (max length %d)", initok, ((int)(sizeof sessions.initok)) - 1);
144 const char *afb_session_initial_token()
146 return sessions.initok;
149 static struct afb_session *search (const char* uuid, int idx)
151 struct afb_session *session;
153 session = sessions.heads[idx];
154 while (session && strcmp(uuid, session->uuid))
155 session = session->next;
160 static void destroy (struct afb_session *session)
162 struct afb_session **prv;
164 assert (session != NULL);
166 close_session(session);
167 pthread_mutex_lock(&sessions.mutex);
168 prv = &sessions.heads[(int)session->idx];
171 prv = &((*prv)->next);
173 *prv = session->next;
175 pthread_mutex_destroy(&session->mutex);
179 pthread_mutex_unlock(&sessions.mutex);
182 // Loop on every entry and remove old context sessions.hash
183 static time_t cleanup ()
185 struct afb_session *session, *next;
189 // Loop on Sessions Table and remove anything that is older than timeout
191 for (idx = 0 ; idx < HEADCOUNT; idx++) {
192 session = sessions.heads[idx];
194 next = session->next;
195 if (session->expiration < now)
196 afb_session_close(session);
203 static void update_timeout(struct afb_session *session, time_t now, int timeout)
207 /* compute expiration */
208 if (timeout == AFB_SESSION_TIMEOUT_INFINITE)
209 expiration = MAX_EXPIRATION;
211 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
212 expiration = now + sessions.timeout;
214 expiration = now + timeout;
216 expiration = MAX_EXPIRATION;
219 /* record the values */
220 session->timeout = timeout;
221 session->expiration = expiration;
224 static void update_expiration(struct afb_session *session, time_t now)
226 update_timeout(session, now, session->timeout);
229 static struct afb_session *add_session (const char *uuid, int timeout, time_t now, int idx)
231 struct afb_session *session;
233 /* check arguments */
234 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
235 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
240 /* check session count */
241 if (sessions.count >= sessions.max) {
246 /* allocates a new one */
247 session = calloc(1, sizeof *session);
248 if (session == NULL) {
254 pthread_mutex_init(&session->mutex, NULL);
255 session->refcount = 1;
256 strcpy(session->uuid, uuid);
257 strcpy(session->token, sessions.initok);
258 update_timeout(session, now, timeout);
261 session->idx = (char)idx;
262 session->next = sessions.heads[idx];
263 sessions.heads[idx] = session;
269 /* create a new session for the given timeout */
270 static struct afb_session *new_session (int timeout, time_t now)
277 idx = pearson4(uuid);
278 } while(search(uuid, idx));
279 return add_session(uuid, timeout, now, idx);
282 /* Creates a new session with 'timeout' */
283 struct afb_session *afb_session_create (int timeout)
286 struct afb_session *session;
289 pthread_mutex_lock(&sessions.mutex);
291 session = new_session(timeout, now);
292 pthread_mutex_unlock(&sessions.mutex);
297 /* Searchs the session of 'uuid' */
298 struct afb_session *afb_session_search (const char *uuid)
300 struct afb_session *session;
303 pthread_mutex_lock(&sessions.mutex);
305 session = search(uuid, pearson4(uuid));
307 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
308 pthread_mutex_unlock(&sessions.mutex);
313 /* This function will return exiting session or newly created session */
314 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
317 struct afb_session *session;
321 pthread_mutex_lock(&sessions.mutex);
324 /* search for an existing one not too old */
326 session = new_session(timeout, now);
328 idx = pearson4(uuid);
329 session = search(uuid, idx);
331 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
332 pthread_mutex_unlock(&sessions.mutex);
337 session = add_session (uuid, timeout, now, idx);
339 pthread_mutex_unlock(&sessions.mutex);
342 *created = !!session;
347 /* increase the use count on the session */
348 struct afb_session *afb_session_addref(struct afb_session *session)
351 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
355 /* decrease the use count of the session */
356 void afb_session_unref(struct afb_session *session)
358 if (session != NULL) {
359 assert(session->refcount != 0);
360 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
361 pthread_mutex_lock(&session->mutex);
362 if (session->autoclose || session->uuid[0] == 0)
365 pthread_mutex_unlock(&session->mutex);
370 // close Client Session Context
371 void afb_session_close (struct afb_session *session)
373 assert(session != NULL);
374 pthread_mutex_lock(&session->mutex);
375 if (session->uuid[0] != 0) {
376 session->uuid[0] = 0;
377 if (session->refcount)
378 close_session(session);
384 pthread_mutex_unlock(&session->mutex);
387 /* set the autoclose flag */
388 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
390 assert(session != NULL);
391 session->autoclose = (char)!!autoclose;
394 // is the session active?
395 int afb_session_is_active (struct afb_session *session)
397 assert(session != NULL);
398 return !!session->uuid[0];
401 // is the session closed?
402 int afb_session_is_closed (struct afb_session *session)
404 assert(session != NULL);
405 return !session->uuid[0];
408 // Sample Generic Ping Debug API
409 int afb_session_check_token (struct afb_session *session, const char *token)
411 assert(session != NULL);
412 assert(token != NULL);
414 if (!session->uuid[0])
417 if (session->expiration < NOW)
420 if (session->token[0] && strcmp (token, session->token) != 0)
426 // generate a new token and update client context
427 void afb_session_new_token (struct afb_session *session)
429 assert(session != NULL);
431 // Old token was valid let's regenerate a new one
432 new_uuid(session->token);
434 // keep track of time for session timeout and further clean up
435 update_expiration(session, NOW);
438 /* Returns the uuid of 'session' */
439 const char *afb_session_uuid (struct afb_session *session)
441 assert(session != NULL);
442 return session->uuid;
445 /* Returns the token of 'session' */
446 const char *afb_session_token (struct afb_session *session)
448 assert(session != NULL);
449 return session->token;
453 * Get the index of the 'key' in the cookies array.
454 * @param key the key to scan
455 * @return the index of the list for key within cookies
457 static int cookeyidx(const void *key)
459 intptr_t x = (intptr_t)key;
460 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
461 return r & COOKEYMASK;
465 * Set, get, replace, remove a cookie of 'key' for the 'session'
467 * The behaviour of this function depends on its parameters:
469 * @param session the session
470 * @param key the key of the cookie
471 * @param makecb the creation function or NULL
472 * @param freecb the release function or NULL
473 * @param closure an argument for makecb or the value if makecb==NULL
474 * @param replace a boolean enforcing replecement of the previous value
476 * @return the value of the cookie
478 * The 'key' is a pointer and compared as pointers.
480 * For getting the current value of the cookie:
482 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
484 * For storing the value of the cookie
486 * afb_session_cookie(session, key, NULL, NULL, value, 1)
488 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
492 struct cookie *cookie, **prv;
494 /* get key hashed index */
495 idx = cookeyidx(key);
497 /* lock session and search for the cookie of 'key' */
499 prv = &session->cookies[idx];
503 /* 'key' not found, create value using 'closure' and 'makecb' */
504 value = makecb ? makecb(closure) : closure;
505 /* store the the only if it has some meaning */
506 if (replace || makecb || freecb) {
507 cookie = malloc(sizeof *cookie);
510 /* calling freecb if there is no makecb may have issue */
511 if (makecb && freecb)
516 cookie->value = value;
517 cookie->freecb = freecb;
523 } else if (cookie->key == key) {
524 /* cookie of key found */
526 /* not replacing, get the value */
527 value = cookie->value;
529 /* create value using 'closure' and 'makecb' */
530 value = makecb ? makecb(closure) : closure;
532 /* free previous value is needed */
533 if (cookie->value != value && cookie->freecb)
534 cookie->freecb(cookie->value);
536 /* store the value and its releaser */
537 cookie->value = value;
538 cookie->freecb = freecb;
540 /* but if both are NULL drop the cookie */
541 if (!value && !freecb) {
548 prv = &(cookie->next);
552 /* unlock the session and return the value */
557 void *afb_session_get_cookie(struct afb_session *session, const void *key)
559 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
562 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
564 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));