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