Events: refactoring
[src/app-framework-binder.git] / src / session.c
1 /*
2  * Copyright (C) 2015, 2016 "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 #include <errno.h>
27
28 #include <json-c/json.h>
29
30 #include "session.h"
31 #include "verbose.h"
32
33 #define NOW (time(NULL))
34
35 struct client_value
36 {
37         void *value;
38         void (*free_value)(void*);
39 };
40
41 struct AFB_clientCtx
42 {
43         unsigned refcount;
44         unsigned loa;
45         time_t expiration;    // expiration time of the token
46         time_t access;
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;
50 };
51
52 // Session UUID are store in a simple array [for 10 sessions this should be enough]
53 static struct {
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
57   int max;
58   int timeout;
59   int apicount;
60   char initok[37];
61 } sessions;
62
63 /* generate a uuid */
64 static void new_uuid(char uuid[37])
65 {
66         uuid_t newuuid;
67         uuid_generate(newuuid);
68         uuid_unparse_lower(newuuid, uuid);
69 }
70
71 // Free context [XXXX Should be protected again memory abort XXXX]
72 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
73 {
74         int idx;
75
76         // If application add a handle let's free it now
77         assert (client->values != NULL);
78
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);
82 }
83
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)
86 {
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;
92         if (initok == NULL)
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);
97         else {
98                 ERROR("initial token '%s' too long (max length 36)", initok);
99                 exit(1);
100         }
101 }
102
103 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
104 {
105     int  idx;
106     struct AFB_clientCtx *client;
107
108     assert (uuid != NULL);
109
110     pthread_mutex_lock(&sessions.mutex);
111
112     for (idx=0; idx < sessions.max; idx++) {
113         client = sessions.store[idx];
114         if (client && (0 == strcmp (uuid, client->uuid)))
115                 goto found;
116     }
117     client = NULL;
118
119 found:
120     pthread_mutex_unlock(&sessions.mutex);
121     return client;
122 }
123
124 static int ctxStoreDel (struct AFB_clientCtx *client)
125 {
126     int idx;
127     int status;
128
129     assert (client != NULL);
130
131     pthread_mutex_lock(&sessions.mutex);
132
133     for (idx=0; idx < sessions.max; idx++) {
134         if (sessions.store[idx] == client) {
135                 sessions.store[idx] = NULL;
136                 sessions.count--;
137                 status = 1;
138                 goto deleted;
139         }
140     }
141     status = 0;
142 deleted:
143     pthread_mutex_unlock(&sessions.mutex);
144     return status;
145 }
146
147 static int ctxStoreAdd (struct AFB_clientCtx *client)
148 {
149     int idx;
150     int status;
151
152     assert (client != NULL);
153
154     pthread_mutex_lock(&sessions.mutex);
155
156     for (idx=0; idx < sessions.max; idx++) {
157         if (NULL == sessions.store[idx]) {
158                 sessions.store[idx] = client;
159                 sessions.count++;
160                 status = 1;
161                 goto added;
162         }
163     }
164     status = 0;
165 added:
166     pthread_mutex_unlock(&sessions.mutex);
167     return status;
168 }
169
170 // Check if context timeout or not
171 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
172 {
173     assert (ctx != NULL);
174     return ctx->expiration < now;
175 }
176
177 // Check if context is active or not
178 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
179 {
180     assert (ctx != NULL);
181     return ctx->uuid[0] != 0 && ctx->expiration >= now;
182 }
183
184 // Loop on every entry and remove old context sessions.hash
185 static void ctxStoreCleanUp (time_t now)
186 {
187         struct AFB_clientCtx *ctx;
188         long idx;
189
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);
195                 }
196         }
197 }
198
199 // This function will return exiting client context or newly created client context
200 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
201 {
202         struct AFB_clientCtx *clientCtx;
203         time_t now;
204
205         /* cleaning */
206         now = NOW;
207         ctxStoreCleanUp (now);
208
209         /* search for an existing one not too old */
210         if (uuid != NULL) {
211                 if (strlen(uuid) >= sizeof clientCtx->uuid) {
212                         errno = EINVAL;
213                         goto error;
214                 }
215                 clientCtx = ctxStoreSearch(uuid);
216                 if (clientCtx != NULL) {
217                         *created = 0;
218                         goto found;
219                 }
220         }
221
222         /* returns a new one */
223         clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
224         if (clientCtx == NULL) {
225                 errno = ENOMEM;
226                 goto error;
227         }
228         clientCtx->values = (void*)(clientCtx + 1);
229
230         /* generate the uuid */
231         if (uuid == NULL) {
232                 new_uuid(clientCtx->uuid);
233         } else {
234                 strcpy(clientCtx->uuid, uuid);
235         }
236
237         /* init the token */
238         strcpy(clientCtx->token, sessions.initok);
239         clientCtx->expiration = now + sessions.timeout;
240         if (!ctxStoreAdd (clientCtx)) {
241                 errno = ENOMEM;
242                 goto error2;
243         }
244         *created = 1;
245
246 found:
247         clientCtx->access = now;
248         clientCtx->refcount++;
249         return clientCtx;
250
251 error2:
252         free(clientCtx);
253 error:
254         return NULL;
255 }
256
257 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
258 {
259         if (clientCtx != NULL)
260                 clientCtx->refcount++;
261         return clientCtx;
262 }
263
264 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
265 {
266         if (clientCtx != NULL) {
267                 assert(clientCtx->refcount != 0);
268                 --clientCtx->refcount;
269                 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
270                         ctxStoreDel (clientCtx);
271                         free(clientCtx);
272                 }
273         }
274 }
275
276 // Free Client Session Context
277 void ctxClientClose (struct AFB_clientCtx *clientCtx)
278 {
279         assert(clientCtx != NULL);
280         if (clientCtx->uuid[0] != 0) {
281                 clientCtx->uuid[0] = 0;
282                 ctxUuidFreeCB (clientCtx);
283                 if (clientCtx->refcount == 0) {
284                         ctxStoreDel (clientCtx);
285                         free(clientCtx);
286                 }
287         }
288 }
289
290 // Sample Generic Ping Debug API
291 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
292 {
293         assert(clientCtx != NULL);
294         assert(token != NULL);
295
296         // compare current token with previous one
297         if (!ctxIsActive (clientCtx, NOW))
298                 return 0;
299
300         if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
301                 return 0;
302
303         return 1;
304 }
305
306 // generate a new token and update client context
307 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
308 {
309         assert(clientCtx != NULL);
310
311         // Old token was valid let's regenerate a new one
312         new_uuid(clientCtx->token);
313
314         // keep track of time for session timeout and further clean up
315         clientCtx->expiration = NOW + sessions.timeout;
316 }
317
318 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
319 {
320         assert(clientCtx != NULL);
321         return clientCtx->uuid;
322 }
323
324 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
325 {
326         assert(clientCtx != NULL);
327         return clientCtx->token;
328 }
329
330 unsigned ctxClientGetLOA (struct AFB_clientCtx *clientCtx)
331 {
332         assert(clientCtx != NULL);
333         return clientCtx->loa;
334 }
335
336 void ctxClientSetLOA (struct AFB_clientCtx *clientCtx, unsigned loa)
337 {
338         assert(clientCtx != NULL);
339         clientCtx->loa = loa;
340 }
341
342 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
343 {
344         assert(clientCtx != NULL);
345         assert(index >= 0);
346         assert(index < sessions.apicount);
347         return clientCtx->values[index].value;
348 }
349
350 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
351 {
352         struct client_value prev;
353         assert(clientCtx != NULL);
354         assert(index >= 0);
355         assert(index < sessions.apicount);
356         prev = clientCtx->values[index];
357         clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
358         if (prev.value != NULL && prev.value != value && prev.free_value != NULL)
359                 prev.free_value(prev.value);
360 }