2 * Copyright (C) 2015, 2016 "IoT.bzh"
3 * Author "Fulup Ar Foll"
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
24 #include <uuid/uuid.h>
28 #include <json-c/json.h>
33 #define NOW (time(NULL))
38 void (*free_value)(void*);
46 void (*free_value)(void*);
54 time_t expiration; // expiration time of the token
56 char uuid[37]; // long term authentication of remote client
57 char token[37]; // short term authentication of remote client
58 struct client_value *values;
59 struct cookie *cookies;
62 // Session UUID are store in a simple array [for 10 sessions this should be enough]
64 pthread_mutex_t mutex; // declare a mutex to protect hash table
65 struct AFB_clientCtx **store; // sessions store
66 int count; // current number of sessions
74 static void new_uuid(char uuid[37])
77 uuid_generate(newuuid);
78 uuid_unparse_lower(newuuid, uuid);
81 // Free context [XXXX Should be protected again memory abort XXXX]
82 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
85 struct cookie *cookie;
87 // If application add a handle let's free it now
88 assert (client->values != NULL);
90 // Free client handle with a standard Free function, with app callback or ignore it
91 for (idx=0; idx < sessions.apicount; idx ++)
92 ctxClientValueSet(client, idx, NULL, NULL);
95 cookie = client->cookies;
96 while (cookie != NULL) {
97 client->cookies = cookie->next;
98 if (cookie->value != NULL && cookie->free_value != NULL)
99 cookie->free_value(cookie->value);
101 cookie = client->cookies;
105 // Create a new store in RAM, not that is too small it will be automatically extended
106 void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count)
108 // let's create as store as hashtable does not have any
109 sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx));
110 sessions.max = max_session_count;
111 sessions.timeout = timeout;
112 sessions.apicount = context_count;
114 /* without token, a secret is made to forbid creation of sessions */
115 new_uuid(sessions.initok);
116 else if (strlen(initok) < sizeof(sessions.store[0]->token))
117 strcpy(sessions.initok, initok);
119 ERROR("initial token '%s' too long (max length 36)", initok);
124 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
127 struct AFB_clientCtx *client;
129 assert (uuid != NULL);
131 pthread_mutex_lock(&sessions.mutex);
133 for (idx=0; idx < sessions.max; idx++) {
134 client = sessions.store[idx];
135 if (client && (0 == strcmp (uuid, client->uuid)))
141 pthread_mutex_unlock(&sessions.mutex);
145 static int ctxStoreDel (struct AFB_clientCtx *client)
150 assert (client != NULL);
152 pthread_mutex_lock(&sessions.mutex);
154 for (idx=0; idx < sessions.max; idx++) {
155 if (sessions.store[idx] == client) {
156 sessions.store[idx] = NULL;
164 pthread_mutex_unlock(&sessions.mutex);
168 static int ctxStoreAdd (struct AFB_clientCtx *client)
173 assert (client != NULL);
175 pthread_mutex_lock(&sessions.mutex);
177 for (idx=0; idx < sessions.max; idx++) {
178 if (NULL == sessions.store[idx]) {
179 sessions.store[idx] = client;
187 pthread_mutex_unlock(&sessions.mutex);
191 // Check if context timeout or not
192 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
194 assert (ctx != NULL);
195 return ctx->expiration < now;
198 // Check if context is active or not
199 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
201 assert (ctx != NULL);
202 return ctx->uuid[0] != 0 && ctx->expiration >= now;
205 // Loop on every entry and remove old context sessions.hash
206 static void ctxStoreCleanUp (time_t now)
208 struct AFB_clientCtx *ctx;
211 // Loop on Sessions Table and remove anything that is older than timeout
212 for (idx=0; idx < sessions.max; idx++) {
213 ctx = sessions.store[idx];
214 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
215 ctxClientClose (ctx);
220 static struct AFB_clientCtx *new_context (const char *uuid, int timeout, time_t now)
222 struct AFB_clientCtx *clientCtx;
224 /* allocates a new one */
225 clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
226 if (clientCtx == NULL) {
230 clientCtx->values = (void*)(clientCtx + 1);
232 /* generate the uuid */
234 new_uuid(clientCtx->uuid);
236 if (strlen(uuid) >= sizeof clientCtx->uuid) {
240 strcpy(clientCtx->uuid, uuid);
244 strcpy(clientCtx->token, sessions.initok);
245 clientCtx->timeout = timeout;
247 clientCtx->expiration = now + timeout;
249 clientCtx->expiration = (time_t)(~(time_t)0);
250 if (clientCtx->expiration < 0)
251 clientCtx->expiration = (time_t)(((unsigned long long)clientCtx->expiration) >> 1);
253 if (!ctxStoreAdd (clientCtx)) {
258 clientCtx->access = now;
259 clientCtx->refcount = 1;
268 struct AFB_clientCtx *ctxClientCreate (const char *uuid, int timeout)
274 ctxStoreCleanUp (now);
276 /* search for an existing one not too old */
277 if (uuid != NULL && ctxStoreSearch(uuid) != NULL) {
282 return new_context(uuid, timeout, now);
285 // This function will return exiting client context or newly created client context
286 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
288 struct AFB_clientCtx *clientCtx;
293 ctxStoreCleanUp (now);
295 /* search for an existing one not too old */
297 clientCtx = ctxStoreSearch(uuid);
298 if (clientCtx != NULL) {
300 clientCtx->access = now;
301 clientCtx->refcount++;
307 return new_context(uuid, sessions.timeout, now);
310 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
312 if (clientCtx != NULL)
313 clientCtx->refcount++;
317 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
319 if (clientCtx != NULL) {
320 assert(clientCtx->refcount != 0);
321 --clientCtx->refcount;
322 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
323 ctxStoreDel (clientCtx);
329 // Free Client Session Context
330 void ctxClientClose (struct AFB_clientCtx *clientCtx)
332 assert(clientCtx != NULL);
333 if (clientCtx->uuid[0] != 0) {
334 clientCtx->uuid[0] = 0;
335 ctxUuidFreeCB (clientCtx);
336 if (clientCtx->refcount == 0) {
337 ctxStoreDel (clientCtx);
343 // Sample Generic Ping Debug API
344 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
346 assert(clientCtx != NULL);
347 assert(token != NULL);
349 // compare current token with previous one
350 if (!ctxIsActive (clientCtx, NOW))
353 if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
359 // generate a new token and update client context
360 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
362 assert(clientCtx != NULL);
364 // Old token was valid let's regenerate a new one
365 new_uuid(clientCtx->token);
367 // keep track of time for session timeout and further clean up
368 if (clientCtx->timeout != 0)
369 clientCtx->expiration = NOW + clientCtx->timeout;
372 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
374 assert(clientCtx != NULL);
375 return clientCtx->uuid;
378 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
380 assert(clientCtx != NULL);
381 return clientCtx->token;
384 unsigned ctxClientGetLOA (struct AFB_clientCtx *clientCtx)
386 assert(clientCtx != NULL);
387 return clientCtx->loa;
390 void ctxClientSetLOA (struct AFB_clientCtx *clientCtx, unsigned loa)
392 assert(clientCtx != NULL);
393 clientCtx->loa = loa;
396 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
398 assert(clientCtx != NULL);
400 assert(index < sessions.apicount);
401 return clientCtx->values[index].value;
404 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
406 struct client_value prev;
407 assert(clientCtx != NULL);
409 assert(index < sessions.apicount);
410 prev = clientCtx->values[index];
411 clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
412 if (prev.value != NULL && prev.value != value && prev.free_value != NULL)
413 prev.free_value(prev.value);
416 void *ctxClientCookieGet(struct AFB_clientCtx *clientCtx, const void *key)
418 struct cookie *cookie;
420 cookie = clientCtx->cookies;
421 while(cookie != NULL) {
422 if (cookie->key == key)
423 return cookie->value;
424 cookie = cookie->next;
429 int ctxClientCookieSet(struct AFB_clientCtx *clientCtx, const void *key, void *value, void (*free_value)(void*))
431 struct cookie *cookie;
433 /* search for a replacement */
434 cookie = clientCtx->cookies;
435 while(cookie != NULL) {
436 if (cookie->key == key) {
437 if (cookie->value != NULL && cookie->value != value && cookie->free_value != NULL)
438 cookie->free_value(cookie->value);
439 cookie->value = value;
440 cookie->free_value = free_value;
443 cookie = cookie->next;
447 cookie = malloc(sizeof *cookie);
448 if (cookie == NULL) {
454 cookie->value = value;
455 cookie->free_value = free_value;
456 cookie->next = clientCtx->cookies;
457 clientCtx->cookies = cookie;