work in progress
[src/app-framework-binder.git] / src / session.c
1 /*
2  * Copyright (C) 2015 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Reference:
19  * http://stackoverflow.com/questions/25971505/how-to-delete-element-from-hsearch
20  *
21  */
22
23 #define _GNU_SOURCE
24 #include <stdio.h>
25 #include <time.h>
26 #include <pthread.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <uuid/uuid.h>
30 #include <assert.h>
31
32
33 /*
34 #include <dirent.h>
35 #include <string.h>
36 #include <sys/stat.h>
37 #include <sys/types.h>
38 #include <search.h>
39 #include <assert.h>
40 */
41
42 #include "afb-apis.h"
43 #include "session.h"
44
45 #define NOW (time(NULL))
46
47 // Session UUID are store in a simple array [for 10 sessions this should be enough]
48 static struct {
49   pthread_mutex_t mutex;          // declare a mutex to protect hash table
50   AFB_clientCtx **store;          // sessions store
51   int count;                      // current number of sessions
52   int max;
53   int timeout;
54   int apicount;
55   const char *initok;
56 } sessions;
57
58 static const char key_uuid[] = "uuid";
59 static const char key_token[] = "token";
60
61 // Free context [XXXX Should be protected again memory abort XXXX]
62 static void ctxUuidFreeCB (AFB_clientCtx *client)
63 {
64     int idx;
65
66     // If application add a handle let's free it now
67     if (client->contexts != NULL) {
68
69         // Free client handle with a standard Free function, with app callback or ignore it
70         for (idx=0; idx < sessions.apicount; idx ++) {
71             if (client->contexts[idx] != NULL) {
72                 afb_apis_free_context(idx, client->contexts[idx]);
73             }
74         }
75     }
76 }
77
78 // Create a new store in RAM, not that is too small it will be automatically extended
79 void ctxStoreInit (int nbSession, int timeout, int apicount, const char *initok)
80 {
81         // let's create as store as hashtable does not have any
82         sessions.store = calloc (1 + (unsigned)nbSession, sizeof(AFB_clientCtx));
83         sessions.max = nbSession;
84         sessions.timeout = timeout;
85         sessions.apicount = apicount;
86         if (strlen(initok) >= 37) {
87                 fprintf(stderr, "Error: initial token '%s' too long (max length 36)", initok);
88                 exit(1);
89         }
90         sessions.initok = initok;
91 }
92
93 static AFB_clientCtx *ctxStoreSearch (const char* uuid)
94 {
95     int  idx;
96     AFB_clientCtx *client;
97
98     assert (uuid != NULL);
99
100     pthread_mutex_lock(&sessions.mutex);
101
102     for (idx=0; idx < sessions.max; idx++) {
103         client = sessions.store[idx];
104         if (client && (0 == strcmp (uuid, client->uuid)))
105                 goto found;
106     }
107     client = NULL;
108
109 found:
110     pthread_mutex_unlock(&sessions.mutex);
111     return client;
112 }
113
114 static int ctxStoreDel (AFB_clientCtx *client)
115 {
116     int idx;
117     int status;
118
119     assert (client != NULL);
120
121     pthread_mutex_lock(&sessions.mutex);
122
123     for (idx=0; idx < sessions.max; idx++) {
124         if (sessions.store[idx] == client) {
125                 sessions.store[idx]=NULL;
126                 sessions.count--;
127                 ctxUuidFreeCB (client);
128                 status = 1;
129                 goto deleted;
130         }
131     }
132     status = 0;
133 deleted:
134     pthread_mutex_unlock(&sessions.mutex);
135     return status;
136 }
137
138 static int ctxStoreAdd (AFB_clientCtx *client)
139 {
140     int idx;
141     int status;
142
143     assert (client != NULL);
144
145     //fprintf (stderr, "ctxStoreAdd request uuid=%s count=%d\n", client->uuid, sessions.count);
146
147     pthread_mutex_lock(&sessions.mutex);
148
149     for (idx=0; idx < sessions.max; idx++) {
150         if (NULL == sessions.store[idx]) {
151                 sessions.store[idx]= client;
152                 sessions.count++;
153                 status = 1;
154                 goto added;
155         }
156     }
157     status = 0;
158 added:
159     pthread_mutex_unlock(&sessions.mutex);
160     return status;
161 }
162
163 // Check if context timeout or not
164 static int ctxStoreTooOld (AFB_clientCtx *ctx, time_t now)
165 {
166     return ctx->timeStamp <= now;
167 }
168
169 // Loop on every entry and remove old context sessions.hash
170 void ctxStoreGarbage ()
171 {
172     AFB_clientCtx *ctx;
173     long idx;
174     time_t now = NOW;
175
176     // Loop on Sessions Table and remove anything that is older than timeout
177     for (idx=0; idx < sessions.max; idx++) {
178         ctx = sessions.store[idx];
179         if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
180             ctxStoreDel (ctx);
181         }
182     }
183 }
184
185 // This function will return exiting client context or newly created client context
186 AFB_clientCtx *ctxClientGet (const char *uuid)
187 {
188         uuid_t newuuid;
189         AFB_clientCtx *clientCtx;
190
191         /* search for an existing one not too old */
192         clientCtx = uuid != NULL ? ctxStoreSearch (uuid) : NULL;
193         if (clientCtx) {
194             if (!ctxStoreTooOld (clientCtx, NOW))
195                 return clientCtx;
196             ctxStoreDel (clientCtx);
197         }
198
199         /* mimic old behaviour */
200         if (sessions.initok == NULL)
201                 return NULL;
202
203         /* cleanup before creating */
204         if(2 * sessions.count >= sessions.max)
205                 ctxStoreGarbage();
206
207         /* returns a new one */
208         clientCtx = calloc(1, sizeof(AFB_clientCtx)); // init NULL clientContext
209         if (clientCtx != NULL) {
210                 clientCtx->contexts = calloc ((unsigned)sessions.apicount, sizeof (void*));
211                 if (clientCtx->contexts != NULL) {
212                         /* generate the uuid */
213                         uuid_generate(newuuid);
214                         uuid_unparse_lower(newuuid, clientCtx->uuid);
215                         clientCtx->timeStamp = time(NULL) + sessions.timeout;
216                         strcpy(clientCtx->token, sessions.initok);
217                         if (ctxStoreAdd (clientCtx))
218                                 return clientCtx;
219                         free(clientCtx->contexts);
220                 }
221                 free(clientCtx);
222         }
223         return NULL;
224 }
225
226 // Free Client Session Context
227 int ctxClientClose (AFB_clientCtx *clientCtx)
228 {
229         assert(clientCtx != NULL);
230         return ctxStoreDel (clientCtx);
231 }
232
233 // Sample Generic Ping Debug API
234 int ctxTokenCheck (AFB_clientCtx *clientCtx, const char *token)
235 {
236         assert(clientCtx != NULL);
237         assert(token != NULL);
238
239         // compare current token with previous one
240         if (ctxStoreTooOld (clientCtx, NOW))
241                 return 0;
242         if (!clientCtx->token[0] || 0 == strcmp (token, clientCtx->token)) {
243                 clientCtx->timeStamp = time(NULL) + sessions.timeout;
244                 return 1;
245         }
246
247         // Token is not valid let move level of assurance to zero and free attached client handle
248         return 0;
249 }
250
251 // generate a new token and update client context
252 int ctxTokenNew (AFB_clientCtx *clientCtx)
253 {
254         uuid_t newuuid;
255
256         assert(clientCtx != NULL);
257
258         // Old token was valid let's regenerate a new one
259         uuid_generate(newuuid);         // create a new UUID
260         uuid_unparse_lower(newuuid, clientCtx->token);
261
262         // keep track of time for session timeout and further clean up
263         clientCtx->timeStamp = time(NULL) + sessions.timeout;
264
265         return 1;
266 }
267