work in progress, session handling
[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, int apicount, 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 = apicount;
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 *ctxClientGet (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         if (sessions.initok == NULL)
188                 return NULL;
189
190         /* check the uuid if given */
191         if (uuid != NULL && 1 + strlen(uuid) >= sizeof clientCtx->uuid)
192                 return NULL;
193
194         /* returns a new one */
195         clientCtx = calloc(1, sizeof(struct AFB_clientCtx)); // init NULL clientContext
196         if (clientCtx != NULL) {
197                 clientCtx->contexts = calloc ((unsigned)sessions.apicount, sizeof (void*));
198                 if (clientCtx->contexts != NULL) {
199                         /* generate the uuid */
200                         if (uuid == NULL) {
201                                 uuid_generate(newuuid);
202                                 uuid_unparse_lower(newuuid, clientCtx->uuid);
203                         } else {
204                                 strcpy(clientCtx->uuid, uuid);
205                         }
206                         strcpy(clientCtx->token, sessions.initok);
207                         clientCtx->expiration = now + sessions.timeout;
208                         clientCtx->refcount = 1;
209                         if (ctxStoreAdd (clientCtx))
210                                 return clientCtx;
211                         free(clientCtx->contexts);
212                 }
213                 free(clientCtx);
214         }
215         return NULL;
216 }
217
218 void ctxClientPut(struct AFB_clientCtx *clientCtx)
219 {
220         if (clientCtx != NULL) {
221                 assert(clientCtx->refcount != 0);
222                 --clientCtx->refcount;
223         }
224 }
225
226 // Free Client Session Context
227 void ctxClientClose (struct AFB_clientCtx *clientCtx)
228 {
229         assert(clientCtx != NULL);
230         if (clientCtx->created) {
231                 clientCtx->created = 0;
232                 ctxUuidFreeCB (clientCtx);
233         }
234         if (clientCtx->refcount == 0)
235                 ctxStoreDel (clientCtx);
236 }
237
238 // Sample Generic Ping Debug API
239 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
240 {
241         assert(clientCtx != NULL);
242         assert(token != NULL);
243
244         // compare current token with previous one
245         if (ctxStoreTooOld (clientCtx, NOW))
246                 return 0;
247
248         if (!clientCtx->token[0] || 0 == strcmp (token, clientCtx->token)) {
249                 clientCtx->created = 1; /* creates by default */
250                 return 1;
251         }
252
253         // Token is not valid let move level of assurance to zero and free attached client handle
254         return 0;
255 }
256
257 // generate a new token and update client context
258 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
259 {
260         uuid_t newuuid;
261
262         assert(clientCtx != NULL);
263
264         // Old token was valid let's regenerate a new one
265         uuid_generate(newuuid);         // create a new UUID
266         uuid_unparse_lower(newuuid, clientCtx->token);
267
268         // keep track of time for session timeout and further clean up
269         clientCtx->expiration = NOW + sessions.timeout;
270 }
271