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"
35 #define COOKEYMASK (COOKEYCOUNT - 1)
37 #define NOW (time(NULL))
44 void (*freecb)(void*);
51 time_t expiration; // expiration time of the token
52 pthread_mutex_t mutex;
53 char uuid[37]; // long term authentication of remote client
54 char token[37]; // short term authentication of remote client
55 struct cookie *cookies[COOKEYCOUNT];
58 // Session UUID are store in a simple array [for 10 sessions this should be enough]
60 pthread_mutex_t mutex; // declare a mutex to protect hash table
61 struct afb_session **store; // sessions store
62 int count; // current number of sessions
69 static void new_uuid(char uuid[37])
72 uuid_generate(newuuid);
73 uuid_unparse_lower(newuuid, uuid);
76 static inline void lock(struct afb_session *session)
78 pthread_mutex_lock(&session->mutex);
81 static inline void unlock(struct afb_session *session)
83 pthread_mutex_unlock(&session->mutex);
86 // Free context [XXXX Should be protected again memory abort XXXX]
87 static void remove_all_cookies(struct afb_session *session)
90 struct cookie *cookie, *next;
93 for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
94 cookie = session->cookies[idx];
95 session->cookies[idx] = NULL;
96 while (cookie != NULL) {
98 if (cookie->freecb != NULL)
99 cookie->freecb(cookie->value);
106 // Create a new store in RAM, not that is too small it will be automatically extended
107 void afb_session_init (int max_session_count, int timeout, const char *initok)
109 // let's create as store as hashtable does not have any
110 sessions.store = calloc (1 + (unsigned)max_session_count, sizeof *sessions.store);
111 pthread_mutex_init(&sessions.mutex, NULL);
112 sessions.max = max_session_count;
113 sessions.timeout = timeout;
115 /* without token, a secret is made to forbid creation of sessions */
116 new_uuid(sessions.initok);
117 else if (strlen(initok) < sizeof(sessions.store[0]->token))
118 strcpy(sessions.initok, initok);
120 ERROR("initial token '%s' too long (max length 36)", initok);
125 const char *afb_session_initial_token()
127 return sessions.initok;
130 static struct afb_session *search (const char* uuid)
133 struct afb_session *session;
135 assert (uuid != NULL);
137 pthread_mutex_lock(&sessions.mutex);
139 for (idx=0; idx < sessions.max; idx++) {
140 session = sessions.store[idx];
141 if (session && (0 == strcmp (uuid, session->uuid)))
147 pthread_mutex_unlock(&sessions.mutex);
151 static int destroy (struct afb_session *session)
156 assert (session != NULL);
158 pthread_mutex_lock(&sessions.mutex);
160 for (idx=0; idx < sessions.max; idx++) {
161 if (sessions.store[idx] == session) {
162 sessions.store[idx] = NULL;
170 pthread_mutex_unlock(&sessions.mutex);
174 static int add (struct afb_session *session)
179 assert (session != NULL);
181 pthread_mutex_lock(&sessions.mutex);
183 for (idx=0; idx < sessions.max; idx++) {
184 if (NULL == sessions.store[idx]) {
185 sessions.store[idx] = session;
193 pthread_mutex_unlock(&sessions.mutex);
197 // Check if context timeout or not
198 static int is_expired (struct afb_session *ctx, time_t now)
200 assert (ctx != NULL);
201 return ctx->expiration < now;
204 // Check if context is active or not
205 static int is_active (struct afb_session *ctx, time_t now)
207 assert (ctx != NULL);
208 return ctx->uuid[0] != 0 && ctx->expiration >= now;
211 // Loop on every entry and remove old context sessions.hash
212 static void cleanup (time_t now)
214 struct afb_session *ctx;
217 // Loop on Sessions Table and remove anything that is older than timeout
218 for (idx=0; idx < sessions.max; idx++) {
219 ctx = sessions.store[idx];
220 if (ctx != NULL && is_expired(ctx, now)) {
221 afb_session_close (ctx);
226 static struct afb_session *make_session (const char *uuid, int timeout, time_t now)
228 struct afb_session *session;
230 if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
231 || (uuid && strlen(uuid) >= sizeof session->uuid)) {
236 /* allocates a new one */
237 session = calloc(1, sizeof *session);
238 if (session == NULL) {
242 pthread_mutex_init(&session->mutex, NULL);
244 /* generate the uuid */
246 do { new_uuid(session->uuid); } while(search(session->uuid));
248 strcpy(session->uuid, uuid);
252 strcpy(session->token, sessions.initok);
255 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
256 timeout = sessions.timeout;
257 session->timeout = timeout;
258 session->expiration = now + timeout;
259 if (timeout == AFB_SESSION_TIMEOUT_INFINITE || session->expiration < 0) {
260 session->expiration = (time_t)(~(time_t)0);
261 if (session->expiration < 0)
262 session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1);
264 if (!add (session)) {
269 session->refcount = 1;
278 /* Creates a new session with 'timeout' */
279 struct afb_session *afb_session_create (int timeout)
287 return make_session(NULL, timeout, now);
290 /* Searchs the session of 'uuid' */
291 struct afb_session *afb_session_search (const char *uuid)
294 struct afb_session *session;
299 session = search(uuid);
304 /* This function will return exiting session or newly created session */
305 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
307 struct afb_session *session;
314 /* search for an existing one not too old */
316 session = search(uuid);
317 if (session != NULL) {
325 /* no existing session found, create it */
326 session = make_session(uuid, timeout, now);
328 *created = !!session;
333 struct afb_session *afb_session_addref(struct afb_session *session)
336 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
340 void afb_session_unref(struct afb_session *session)
342 if (session != NULL) {
343 assert(session->refcount != 0);
344 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
345 if (session->uuid[0] == 0) {
347 pthread_mutex_destroy(&session->mutex);
354 // Free Client Session Context
355 void afb_session_close (struct afb_session *session)
357 assert(session != NULL);
358 if (session->uuid[0] != 0) {
359 session->uuid[0] = 0;
360 remove_all_cookies(session);
361 if (session->refcount == 0) {
368 // Sample Generic Ping Debug API
369 int afb_session_check_token (struct afb_session *session, const char *token)
371 assert(session != NULL);
372 assert(token != NULL);
374 // compare current token with previous one
375 if (!is_active (session, NOW))
378 if (session->token[0] && strcmp (token, session->token) != 0)
384 // generate a new token and update client context
385 void afb_session_new_token (struct afb_session *session)
387 assert(session != NULL);
389 // Old token was valid let's regenerate a new one
390 new_uuid(session->token);
392 // keep track of time for session timeout and further clean up
393 if (session->timeout != 0)
394 session->expiration = NOW + session->timeout;
397 /* Returns the uuid of 'session' */
398 const char *afb_session_uuid (struct afb_session *session)
400 assert(session != NULL);
401 return session->uuid;
404 /* Returns the token of 'session' */
405 const char *afb_session_token (struct afb_session *session)
407 assert(session != NULL);
408 return session->token;
412 * Get the index of the 'key' in the cookies array.
413 * @param key the key to scan
414 * @return the index of the list for key within cookies
416 static int cookeyidx(const void *key)
418 intptr_t x = (intptr_t)key;
419 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
420 return r & COOKEYMASK;
424 * Set, get, replace, remove a cookie of 'key' for the 'session'
426 * The behaviour of this function depends on its parameters:
428 * @param session the session
429 * @param key the key of the cookie
430 * @param makecb the creation function
431 * @param freecb the release function
432 * @param closure an argument
433 * @param replace a boolean enforcing replecement of the previous value
435 * @return the value of the cookie
437 * The 'key' is a pointer and compared as pointers.
439 * For getting the current value of the cookie:
441 * afb_session_cookie(session, key, NULL, NULL, NULL, 0)
443 * For storing the value of the cookie
445 * afb_session_cookie(session, key, NULL, NULL, value, 1)
447 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
451 struct cookie *cookie, **prv;
453 /* get key hashed index */
454 idx = cookeyidx(key);
456 /* lock session and search for the cookie of 'key' */
458 prv = &session->cookies[idx];
462 /* 'key' not found, create value using 'closure' and 'makecb' */
463 value = makecb ? makecb(closure) : closure;
464 /* store the the only if it has some meaning */
465 if (replace || makecb || freecb) {
466 cookie = malloc(sizeof *cookie);
469 /* calling freecb if there is no makecb may have issue */
470 if (makecb && freecb)
475 cookie->value = value;
476 cookie->freecb = freecb;
482 } else if (cookie->key == key) {
483 /* cookie of key found */
485 /* not replacing, get the value */
486 value = cookie->value;
488 /* create value using 'closure' and 'makecb' */
489 value = makecb ? makecb(closure) : closure;
491 /* free previous value is needed */
492 if (cookie->value != value && cookie->freecb)
493 cookie->freecb(cookie->value);
495 /* store the value and its releaser */
496 cookie->value = value;
497 cookie->freecb = freecb;
499 /* but if both are NULL drop the cookie */
500 if (!value && !freecb) {
507 prv = &(cookie->next);
511 /* unlock the session and return the value */
516 void *afb_session_get_cookie(struct afb_session *session, const void *key)
518 return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
521 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
523 return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));