e30688ab3e2e5939b0fe3b55ec3fd5403ca8ac64
[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 #include "afb-apis.h"
33 #include "session.h"
34
35 #define NOW (time(NULL))
36
37 // Session UUID are store in a simple array [for 10 sessions this should be enough]
38 static struct {
39   pthread_mutex_t mutex;          // declare a mutex to protect hash table
40   struct AFB_clientCtx **store;          // sessions store
41   int count;                      // current number of sessions
42   int max;
43   int timeout;
44   int apicount;
45   const char *initok;
46 } sessions;
47
48 // Free context [XXXX Should be protected again memory abort XXXX]
49 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
50 {
51         int idx;
52
53         // If application add a handle let's free it now
54         assert (client->contexts != NULL);
55
56         // Free client handle with a standard Free function, with app callback or ignore it
57         for (idx=0; idx < sessions.apicount; idx ++) {
58                 if (client->contexts[idx] != NULL) {
59                         afb_apis_free_context(idx, client->contexts[idx]);
60                         client->contexts[idx] = NULL;
61                 }
62         }
63 }
64
65 // Create a new store in RAM, not that is too small it will be automatically extended
66 void ctxStoreInit (int nbSession, int timeout, const char *initok)
67 {
68         // let's create as store as hashtable does not have any
69         sessions.store = calloc (1 + (unsigned)nbSession, sizeof(struct AFB_clientCtx));
70         sessions.max = nbSession;
71         sessions.timeout = timeout;
72         sessions.apicount = afb_apis_count();
73         if (strlen(initok) >= 37) {
74                 fprintf(stderr, "Error: initial token '%s' too long (max length 36)", initok);
75                 exit(1);
76         }
77         sessions.initok = initok;
78 }
79
80 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
81 {
82     int  idx;
83     struct AFB_clientCtx *client;
84
85     assert (uuid != NULL);
86
87     pthread_mutex_lock(&sessions.mutex);
88
89     for (idx=0; idx < sessions.max; idx++) {
90         client = sessions.store[idx];
91         if (client && (0 == strcmp (uuid, client->uuid)))
92                 goto found;
93     }
94     client = NULL;
95
96 found:
97     pthread_mutex_unlock(&sessions.mutex);
98     return client;
99 }
100
101 static int ctxStoreDel (struct AFB_clientCtx *client)
102 {
103     int idx;
104     int status;
105
106     assert (client != NULL);
107
108     pthread_mutex_lock(&sessions.mutex);
109
110     for (idx=0; idx < sessions.max; idx++) {
111         if (sessions.store[idx] == client) {
112                 sessions.store[idx]=NULL;
113                 sessions.count--;
114                 status = 1;
115                 goto deleted;
116         }
117     }
118     status = 0;
119 deleted:
120     pthread_mutex_unlock(&sessions.mutex);
121     return status;
122 }
123
124 static int ctxStoreAdd (struct AFB_clientCtx *client)
125 {
126     int idx;
127     int status;
128
129     assert (client != NULL);
130
131     //fprintf (stderr, "ctxStoreAdd request uuid=%s count=%d\n", client->uuid, sessions.count);
132
133     pthread_mutex_lock(&sessions.mutex);
134
135     for (idx=0; idx < sessions.max; idx++) {
136         if (NULL == sessions.store[idx]) {
137                 sessions.store[idx]= client;
138                 sessions.count++;
139                 status = 1;
140                 goto added;
141         }
142     }
143     status = 0;
144 added:
145     pthread_mutex_unlock(&sessions.mutex);
146     return status;
147 }
148
149 // Check if context timeout or not
150 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
151 {
152     return ctx->expiration <= now;
153 }
154
155 // Loop on every entry and remove old context sessions.hash
156 static void ctxStoreCleanUp (time_t now)
157 {
158         struct AFB_clientCtx *ctx;
159         long idx;
160
161         // Loop on Sessions Table and remove anything that is older than timeout
162         for (idx=0; idx < sessions.max; idx++) {
163                 ctx = sessions.store[idx];
164                 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
165                         ctxClientClose (ctx);
166                 }
167         }
168 }
169
170 // This function will return exiting client context or newly created client context
171 struct AFB_clientCtx *ctxClientGetForUuid (const char *uuid)
172 {
173         uuid_t newuuid;
174         struct AFB_clientCtx *clientCtx;
175         time_t now;
176
177         /* search for an existing one not too old */
178         now = NOW;
179         ctxStoreCleanUp (now);
180         clientCtx = uuid != NULL ? ctxStoreSearch (uuid) : NULL;
181         if (clientCtx) {
182                 clientCtx->refcount++;
183                 return clientCtx;
184         }
185
186         /* mimic old behaviour */
187 /*
188 TODO remove? not remove?
189         if (sessions.initok == NULL)
190                 return NULL;
191 */
192         /* check the uuid if given */
193         if (uuid != NULL && strlen(uuid) >= sizeof clientCtx->uuid)
194                 return NULL;
195
196         /* returns a new one */
197         clientCtx = calloc(1, sizeof(struct AFB_clientCtx)); // init NULL clientContext
198         if (clientCtx != NULL) {
199                 clientCtx->contexts = calloc ((unsigned)sessions.apicount, sizeof (void*));
200                 if (clientCtx->contexts != NULL) {
201                         /* generate the uuid */
202                         if (uuid == NULL) {
203                                 uuid_generate(newuuid);
204                                 uuid_unparse_lower(newuuid, clientCtx->uuid);
205                         } else {
206                                 strcpy(clientCtx->uuid, uuid);
207                         }
208                         strcpy(clientCtx->token, sessions.initok);
209                         clientCtx->expiration = now + sessions.timeout;
210                         clientCtx->refcount = 1;
211                         if (ctxStoreAdd (clientCtx))
212                                 return clientCtx;
213                         free(clientCtx->contexts);
214                 }
215                 free(clientCtx);
216         }
217         return NULL;
218 }
219
220 struct AFB_clientCtx *ctxClientGet(struct AFB_clientCtx *clientCtx)
221 {
222         if (clientCtx != NULL)
223                 clientCtx->refcount++;
224         return clientCtx;
225 }
226
227 void ctxClientPut(struct AFB_clientCtx *clientCtx)
228 {
229         if (clientCtx != NULL) {
230                 assert(clientCtx->refcount != 0);
231                 --clientCtx->refcount;
232         }
233 }
234
235 // Free Client Session Context
236 void ctxClientClose (struct AFB_clientCtx *clientCtx)
237 {
238         assert(clientCtx != NULL);
239         if (clientCtx->created) {
240                 clientCtx->created = 0;
241                 ctxUuidFreeCB (clientCtx);
242         }
243         if (clientCtx->refcount == 0)
244                 ctxStoreDel (clientCtx);
245 }
246
247 // Sample Generic Ping Debug API
248 int ctxTokenCheckLen (struct AFB_clientCtx *clientCtx, const char *token, size_t length)
249 {
250         assert(clientCtx != NULL);
251         assert(token != NULL);
252
253         // compare current token with previous one
254         if (ctxStoreTooOld (clientCtx, NOW))
255                 return 0;
256
257         if (clientCtx->token[0] && (length >= sizeof(clientCtx->token) || strncmp (token, clientCtx->token, length) || clientCtx->token[length]))
258                 return 0;
259
260         clientCtx->created = 1; /* creates by default */
261         return 1;
262 }
263
264 // Sample Generic Ping Debug API
265 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
266 {
267         assert(clientCtx != NULL);
268         assert(token != NULL);
269
270         return ctxTokenCheckLen(clientCtx, token, strlen(token));
271 }
272
273 // generate a new token and update client context
274 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
275 {
276         uuid_t newuuid;
277
278         assert(clientCtx != NULL);
279
280         // Old token was valid let's regenerate a new one
281         uuid_generate(newuuid);         // create a new UUID
282         uuid_unparse_lower(newuuid, clientCtx->token);
283
284         // keep track of time for session timeout and further clean up
285         clientCtx->expiration = NOW + sessions.timeout;
286 }
287