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*);
52 time_t expiration; // expiration time of the token
54 char uuid[37]; // long term authentication of remote client
55 char token[37]; // short term authentication of remote client
56 struct cookie *cookies[COOKEYCOUNT];
59 // Session UUID are store in a simple array [for 10 sessions this should be enough]
61 pthread_mutex_t mutex; // declare a mutex to protect hash table
62 struct afb_session **store; // sessions store
63 int count; // current number of sessions
70 * Get the index of the 'key' in the cookies array.
71 * @param key the key to scan
72 * @return the index of the list for key within cookies
74 static int cookeyidx(const void *key)
76 intptr_t x = (intptr_t)key;
77 unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
78 return r & COOKEYMASK;
82 static void new_uuid(char uuid[37])
85 uuid_generate(newuuid);
86 uuid_unparse_lower(newuuid, uuid);
89 // Free context [XXXX Should be protected again memory abort XXXX]
90 static void free_data (struct afb_session *session)
93 struct cookie *cookie, *next;
96 for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
97 cookie = session->cookies[idx];
98 session->cookies[idx] = NULL;
99 while (cookie != NULL) {
101 if (cookie->freecb != NULL)
102 cookie->freecb(cookie->value);
109 // Create a new store in RAM, not that is too small it will be automatically extended
110 void afb_session_init (int max_session_count, int timeout, const char *initok)
112 // let's create as store as hashtable does not have any
113 sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct afb_session));
114 sessions.max = max_session_count;
115 sessions.timeout = timeout;
117 /* without token, a secret is made to forbid creation of sessions */
118 new_uuid(sessions.initok);
119 else if (strlen(initok) < sizeof(sessions.store[0]->token))
120 strcpy(sessions.initok, initok);
122 ERROR("initial token '%s' too long (max length 36)", initok);
127 static struct afb_session *search (const char* uuid)
130 struct afb_session *session;
132 assert (uuid != NULL);
134 pthread_mutex_lock(&sessions.mutex);
136 for (idx=0; idx < sessions.max; idx++) {
137 session = sessions.store[idx];
138 if (session && (0 == strcmp (uuid, session->uuid)))
144 pthread_mutex_unlock(&sessions.mutex);
148 static int destroy (struct afb_session *session)
153 assert (session != NULL);
155 pthread_mutex_lock(&sessions.mutex);
157 for (idx=0; idx < sessions.max; idx++) {
158 if (sessions.store[idx] == session) {
159 sessions.store[idx] = NULL;
167 pthread_mutex_unlock(&sessions.mutex);
171 static int add (struct afb_session *session)
176 assert (session != NULL);
178 pthread_mutex_lock(&sessions.mutex);
180 for (idx=0; idx < sessions.max; idx++) {
181 if (NULL == sessions.store[idx]) {
182 sessions.store[idx] = session;
190 pthread_mutex_unlock(&sessions.mutex);
194 // Check if context timeout or not
195 static int is_expired (struct afb_session *ctx, time_t now)
197 assert (ctx != NULL);
198 return ctx->expiration < now;
201 // Check if context is active or not
202 static int is_active (struct afb_session *ctx, time_t now)
204 assert (ctx != NULL);
205 return ctx->uuid[0] != 0 && ctx->expiration >= now;
208 // Loop on every entry and remove old context sessions.hash
209 static void cleanup (time_t now)
211 struct afb_session *ctx;
214 // Loop on Sessions Table and remove anything that is older than timeout
215 for (idx=0; idx < sessions.max; idx++) {
216 ctx = sessions.store[idx];
217 if (ctx != NULL && is_expired(ctx, now)) {
218 afb_session_close (ctx);
223 static struct afb_session *make_session (const char *uuid, int timeout, time_t now)
225 struct afb_session *session;
227 /* allocates a new one */
228 session = calloc(1, sizeof(struct afb_session));
229 if (session == NULL) {
234 /* generate the uuid */
236 new_uuid(session->uuid);
238 if (strlen(uuid) >= sizeof session->uuid) {
242 strcpy(session->uuid, uuid);
246 strcpy(session->token, sessions.initok);
247 session->timeout = timeout;
249 session->expiration = now + timeout;
251 session->expiration = (time_t)(~(time_t)0);
252 if (session->expiration < 0)
253 session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1);
255 if (!add (session)) {
260 session->access = now;
261 session->refcount = 1;
270 struct afb_session *afb_session_create (const char *uuid, int timeout)
278 /* search for an existing one not too old */
279 if (uuid != NULL && search(uuid) != NULL) {
284 return make_session(uuid, timeout, now);
287 // This function will return exiting session or newly created session
288 struct afb_session *afb_session_get (const char *uuid, int *created)
290 struct afb_session *session;
297 /* search for an existing one not too old */
299 session = search(uuid);
300 if (session != NULL) {
302 session->access = now;
309 return make_session(uuid, sessions.timeout, now);
312 struct afb_session *afb_session_addref(struct afb_session *session)
319 void afb_session_unref(struct afb_session *session)
321 if (session != NULL) {
322 assert(session->refcount != 0);
324 if (session->refcount == 0 && session->uuid[0] == 0) {
331 // Free Client Session Context
332 void afb_session_close (struct afb_session *session)
334 assert(session != NULL);
335 if (session->uuid[0] != 0) {
336 session->uuid[0] = 0;
338 if (session->refcount == 0) {
345 // Sample Generic Ping Debug API
346 int afb_session_check_token (struct afb_session *session, const char *token)
348 assert(session != NULL);
349 assert(token != NULL);
351 // compare current token with previous one
352 if (!is_active (session, NOW))
355 if (session->token[0] && strcmp (token, session->token) != 0)
361 // generate a new token and update client context
362 void afb_session_new_token (struct afb_session *session)
364 assert(session != NULL);
366 // Old token was valid let's regenerate a new one
367 new_uuid(session->token);
369 // keep track of time for session timeout and further clean up
370 if (session->timeout != 0)
371 session->expiration = NOW + session->timeout;
374 const char *afb_session_uuid (struct afb_session *session)
376 assert(session != NULL);
377 return session->uuid;
380 const char *afb_session_token (struct afb_session *session)
382 assert(session != NULL);
383 return session->token;
386 unsigned afb_session_get_LOA (struct afb_session *session)
388 assert(session != NULL);
392 void afb_session_set_LOA (struct afb_session *session, unsigned loa)
394 assert(session != NULL);
398 void *afb_session_get_cookie(struct afb_session *session, const void *key)
400 struct cookie *cookie;
403 idx = cookeyidx(key);
404 cookie = session->cookies[idx];
405 while(cookie != NULL) {
406 if (cookie->key == key)
407 return cookie->value;
408 cookie = cookie->next;
413 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
415 struct cookie *cookie;
418 /* search for a replacement */
419 idx = cookeyidx(key);
420 cookie = session->cookies[idx];
421 while(cookie != NULL) {
422 if (cookie->key == key) {
423 if (cookie->value != value && cookie->freecb)
424 cookie->freecb(cookie->value);
425 cookie->value = value;
426 cookie->freecb = freecb;
429 cookie = cookie->next;
433 cookie = malloc(sizeof *cookie);
434 if (cookie == NULL) {
440 cookie->value = value;
441 cookie->freecb = freecb;
442 cookie->next = session->cookies[idx];
443 session->cookies[idx] = cookie;