/* * Copyright (C) 2015 "IoT.bzh" * Author "Fulup Ar Foll" * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include "session.h" #define NOW (time(NULL)) // 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_clientCtx **store; // sessions store int count; // current number of sessions int max; int timeout; int apicount; const char *initok; } sessions; void *afb_context_get(struct afb_context *actx) { return actx->context; } void afb_context_set(struct afb_context *actx, void *context, void (*free_context)(void*)) { fprintf(stderr, "afb_context_set(%p,%p) was (%p,%p)\n",context, free_context, actx->context, actx->free_context); if (actx->context != NULL && actx->free_context != NULL) actx->free_context(actx->context); actx->context = context; actx->free_context = free_context; } // Free context [XXXX Should be protected again memory abort XXXX] static void ctxUuidFreeCB (struct AFB_clientCtx *client) { int idx; // If application add a handle let's free it now assert (client->contexts != NULL); // Free client handle with a standard Free function, with app callback or ignore it for (idx=0; idx < sessions.apicount; idx ++) afb_context_set(&client->contexts[idx], NULL, NULL); } // Create a new store in RAM, not that is too small it will be automatically extended void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count) { // let's create as store as hashtable does not have any sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx)); sessions.max = max_session_count; sessions.timeout = timeout; sessions.apicount = context_count; if (strlen(initok) >= 37) { fprintf(stderr, "Error: initial token '%s' too long (max length 36)", initok); exit(1); } sessions.initok = initok; } static struct AFB_clientCtx *ctxStoreSearch (const char* uuid) { int idx; struct AFB_clientCtx *client; assert (uuid != NULL); pthread_mutex_lock(&sessions.mutex); for (idx=0; idx < sessions.max; idx++) { client = sessions.store[idx]; if (client && (0 == strcmp (uuid, client->uuid))) goto found; } client = NULL; found: pthread_mutex_unlock(&sessions.mutex); return client; } static int ctxStoreDel (struct AFB_clientCtx *client) { int idx; int status; assert (client != NULL); pthread_mutex_lock(&sessions.mutex); for (idx=0; idx < sessions.max; idx++) { if (sessions.store[idx] == client) { sessions.store[idx]=NULL; sessions.count--; status = 1; goto deleted; } } status = 0; deleted: pthread_mutex_unlock(&sessions.mutex); return status; } static int ctxStoreAdd (struct AFB_clientCtx *client) { int idx; int status; assert (client != NULL); //fprintf (stderr, "ctxStoreAdd request uuid=%s count=%d\n", client->uuid, sessions.count); pthread_mutex_lock(&sessions.mutex); for (idx=0; idx < sessions.max; idx++) { if (NULL == sessions.store[idx]) { sessions.store[idx]= client; sessions.count++; status = 1; goto added; } } status = 0; added: pthread_mutex_unlock(&sessions.mutex); return status; } // Check if context timeout or not static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now) { return ctx->expiration <= now; } // Loop on every entry and remove old context sessions.hash static void ctxStoreCleanUp (time_t now) { struct AFB_clientCtx *ctx; long idx; // 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 && ctxStoreTooOld(ctx, now)) { ctxClientClose (ctx); } } } // This function will return exiting client context or newly created client context struct AFB_clientCtx *ctxClientGetForUuid (const char *uuid) { uuid_t newuuid; struct AFB_clientCtx *clientCtx; time_t now; /* search for an existing one not too old */ now = NOW; ctxStoreCleanUp (now); clientCtx = uuid != NULL ? ctxStoreSearch (uuid) : NULL; if (clientCtx) { clientCtx->refcount++; return clientCtx; } /* mimic old behaviour */ /* TODO remove? not remove? if (sessions.initok == NULL) return NULL; */ /* check the uuid if given */ if (uuid != NULL && strlen(uuid) >= sizeof clientCtx->uuid) return NULL; /* returns a new one */ clientCtx = calloc(1, sizeof(struct AFB_clientCtx)); // init NULL clientContext if (clientCtx != NULL) { clientCtx->contexts = calloc ((unsigned)sessions.apicount, sizeof(*clientCtx->contexts)); if (clientCtx->contexts != NULL) { /* generate the uuid */ if (uuid == NULL) { uuid_generate(newuuid); uuid_unparse_lower(newuuid, clientCtx->uuid); } else { strcpy(clientCtx->uuid, uuid); } strcpy(clientCtx->token, sessions.initok); clientCtx->expiration = now + sessions.timeout; clientCtx->refcount = 1; if (ctxStoreAdd (clientCtx)) return clientCtx; free(clientCtx->contexts); } free(clientCtx); } return NULL; } struct AFB_clientCtx *ctxClientGet(struct AFB_clientCtx *clientCtx) { if (clientCtx != NULL) clientCtx->refcount++; return clientCtx; } void ctxClientPut(struct AFB_clientCtx *clientCtx) { if (clientCtx != NULL) { assert(clientCtx->refcount != 0); --clientCtx->refcount; } } // Free Client Session Context void ctxClientClose (struct AFB_clientCtx *clientCtx) { assert(clientCtx != NULL); if (clientCtx->created) { clientCtx->created = 0; ctxUuidFreeCB (clientCtx); } if (clientCtx->refcount == 0) ctxStoreDel (clientCtx); } // Sample Generic Ping Debug API int ctxTokenCheckLen (struct AFB_clientCtx *clientCtx, const char *token, size_t length) { assert(clientCtx != NULL); assert(token != NULL); // compare current token with previous one if (ctxStoreTooOld (clientCtx, NOW)) return 0; if (clientCtx->token[0] && (length >= sizeof(clientCtx->token) || strncmp (token, clientCtx->token, length) || clientCtx->token[length])) return 0; clientCtx->created = 1; /* creates by default */ return 1; } // Sample Generic Ping Debug API int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token) { assert(clientCtx != NULL); assert(token != NULL); return ctxTokenCheckLen(clientCtx, token, strlen(token)); } // generate a new token and update client context void ctxTokenNew (struct AFB_clientCtx *clientCtx) { uuid_t newuuid; assert(clientCtx != NULL); // Old token was valid let's regenerate a new one uuid_generate(newuuid); // create a new UUID uuid_unparse_lower(newuuid, clientCtx->token); // keep track of time for session timeout and further clean up clientCtx->expiration = NOW + sessions.timeout; }