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*);
45 time_t expiration; // expiration time of the token
47 char uuid[37]; // long term authentication of remote client
48 char token[37]; // short term authentication of remote client
49 struct client_value *values;
52 // Session UUID are store in a simple array [for 10 sessions this should be enough]
54 pthread_mutex_t mutex; // declare a mutex to protect hash table
55 struct AFB_clientCtx **store; // sessions store
56 int count; // current number of sessions
64 static void new_uuid(char uuid[37])
67 uuid_generate(newuuid);
68 uuid_unparse_lower(newuuid, uuid);
71 // Free context [XXXX Should be protected again memory abort XXXX]
72 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
76 // If application add a handle let's free it now
77 assert (client->values != NULL);
79 // Free client handle with a standard Free function, with app callback or ignore it
80 for (idx=0; idx < sessions.apicount; idx ++)
81 ctxClientValueSet(client, idx, NULL, NULL);
84 // Create a new store in RAM, not that is too small it will be automatically extended
85 void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count)
87 // let's create as store as hashtable does not have any
88 sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx));
89 sessions.max = max_session_count;
90 sessions.timeout = timeout;
91 sessions.apicount = context_count;
93 /* without token, a secret is made to forbid creation of sessions */
94 new_uuid(sessions.initok);
95 else if (strlen(initok) < sizeof(sessions.store[0]->token))
96 strcpy(sessions.initok, initok);
98 ERROR("initial token '%s' too long (max length 36)", initok);
103 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
106 struct AFB_clientCtx *client;
108 assert (uuid != NULL);
110 pthread_mutex_lock(&sessions.mutex);
112 for (idx=0; idx < sessions.max; idx++) {
113 client = sessions.store[idx];
114 if (client && (0 == strcmp (uuid, client->uuid)))
120 pthread_mutex_unlock(&sessions.mutex);
124 static int ctxStoreDel (struct AFB_clientCtx *client)
129 assert (client != NULL);
131 pthread_mutex_lock(&sessions.mutex);
133 for (idx=0; idx < sessions.max; idx++) {
134 if (sessions.store[idx] == client) {
135 sessions.store[idx] = NULL;
143 pthread_mutex_unlock(&sessions.mutex);
147 static int ctxStoreAdd (struct AFB_clientCtx *client)
152 assert (client != NULL);
154 pthread_mutex_lock(&sessions.mutex);
156 for (idx=0; idx < sessions.max; idx++) {
157 if (NULL == sessions.store[idx]) {
158 sessions.store[idx] = client;
166 pthread_mutex_unlock(&sessions.mutex);
170 // Check if context timeout or not
171 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
173 assert (ctx != NULL);
174 return ctx->expiration < now;
177 // Check if context is active or not
178 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
180 assert (ctx != NULL);
181 return ctx->uuid[0] != 0 && ctx->expiration >= now;
184 // Loop on every entry and remove old context sessions.hash
185 static void ctxStoreCleanUp (time_t now)
187 struct AFB_clientCtx *ctx;
190 // Loop on Sessions Table and remove anything that is older than timeout
191 for (idx=0; idx < sessions.max; idx++) {
192 ctx = sessions.store[idx];
193 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
194 ctxClientClose (ctx);
199 static struct AFB_clientCtx *new_context (const char *uuid, int timeout, time_t now)
201 struct AFB_clientCtx *clientCtx;
203 /* allocates a new one */
204 clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
205 if (clientCtx == NULL) {
209 clientCtx->values = (void*)(clientCtx + 1);
211 /* generate the uuid */
213 new_uuid(clientCtx->uuid);
215 if (strlen(uuid) >= sizeof clientCtx->uuid) {
219 strcpy(clientCtx->uuid, uuid);
223 strcpy(clientCtx->token, sessions.initok);
224 clientCtx->expiration = now + sessions.timeout;
225 if (!ctxStoreAdd (clientCtx)) {
230 clientCtx->access = now;
231 clientCtx->refcount = 1;
240 struct AFB_clientCtx *ctxClientCreate (const char *uuid, int timeout)
246 ctxStoreCleanUp (now);
248 /* search for an existing one not too old */
249 if (uuid != NULL && ctxStoreSearch(uuid) != NULL) {
254 return new_context(uuid, timeout, now);
257 // This function will return exiting client context or newly created client context
258 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
260 struct AFB_clientCtx *clientCtx;
265 ctxStoreCleanUp (now);
267 /* search for an existing one not too old */
269 clientCtx = ctxStoreSearch(uuid);
270 if (clientCtx != NULL) {
272 clientCtx->access = now;
273 clientCtx->refcount++;
279 return new_context(uuid, sessions.timeout, now);
282 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
284 if (clientCtx != NULL)
285 clientCtx->refcount++;
289 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
291 if (clientCtx != NULL) {
292 assert(clientCtx->refcount != 0);
293 --clientCtx->refcount;
294 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
295 ctxStoreDel (clientCtx);
301 // Free Client Session Context
302 void ctxClientClose (struct AFB_clientCtx *clientCtx)
304 assert(clientCtx != NULL);
305 if (clientCtx->uuid[0] != 0) {
306 clientCtx->uuid[0] = 0;
307 ctxUuidFreeCB (clientCtx);
308 if (clientCtx->refcount == 0) {
309 ctxStoreDel (clientCtx);
315 // Sample Generic Ping Debug API
316 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
318 assert(clientCtx != NULL);
319 assert(token != NULL);
321 // compare current token with previous one
322 if (!ctxIsActive (clientCtx, NOW))
325 if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
331 // generate a new token and update client context
332 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
334 assert(clientCtx != NULL);
336 // Old token was valid let's regenerate a new one
337 new_uuid(clientCtx->token);
339 // keep track of time for session timeout and further clean up
340 clientCtx->expiration = NOW + sessions.timeout;
343 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
345 assert(clientCtx != NULL);
346 return clientCtx->uuid;
349 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
351 assert(clientCtx != NULL);
352 return clientCtx->token;
355 unsigned ctxClientGetLOA (struct AFB_clientCtx *clientCtx)
357 assert(clientCtx != NULL);
358 return clientCtx->loa;
361 void ctxClientSetLOA (struct AFB_clientCtx *clientCtx, unsigned loa)
363 assert(clientCtx != NULL);
364 clientCtx->loa = loa;
367 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
369 assert(clientCtx != NULL);
371 assert(index < sessions.apicount);
372 return clientCtx->values[index].value;
375 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
377 struct client_value prev;
378 assert(clientCtx != NULL);
380 assert(index < sessions.apicount);
381 prev = clientCtx->values[index];
382 clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
383 if (prev.value != NULL && prev.value != value && prev.free_value != NULL)
384 prev.free_value(prev.value);