Improves naming of session's module
[src/app-framework-binder.git] / src / afb-session.c
1 /*
2  * Copyright (C) 2015, 2016, 2017 "IoT.bzh"
3  * Author "Fulup Ar Foll"
4  * Author: José Bollo <jose.bollo@iot.bzh>
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *   http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #define _GNU_SOURCE
20 #include <stdio.h>
21 #include <time.h>
22 #include <pthread.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <uuid/uuid.h>
26 #include <assert.h>
27 #include <errno.h>
28
29 #include <json-c/json.h>
30
31 #include "afb-session.h"
32 #include "verbose.h"
33
34 #define NOW (time(NULL))
35
36 struct value
37 {
38         void *value;
39         void (*freecb)(void*);
40 };
41
42 struct cookie
43 {
44         struct cookie *next;
45         const void *key;
46         void *value;
47         void (*freecb)(void*);
48 };
49
50 struct afb_session
51 {
52         unsigned refcount;
53         unsigned loa;
54         int timeout;
55         time_t expiration;    // expiration time of the token
56         time_t access;
57         char uuid[37];        // long term authentication of remote client
58         char token[37];       // short term authentication of remote client
59         struct value *values;
60         struct cookie *cookies;
61 };
62
63 // Session UUID are store in a simple array [for 10 sessions this should be enough]
64 static struct {
65         pthread_mutex_t mutex;          // declare a mutex to protect hash table
66         struct afb_session **store;          // sessions store
67         int count;                      // current number of sessions
68         int max;
69         int timeout;
70         int apicount;
71         char initok[37];
72 } sessions;
73
74 /* generate a uuid */
75 static void new_uuid(char uuid[37])
76 {
77         uuid_t newuuid;
78         uuid_generate(newuuid);
79         uuid_unparse_lower(newuuid, uuid);
80 }
81
82 // Free context [XXXX Should be protected again memory abort XXXX]
83 static void free_data (struct afb_session *session)
84 {
85         int idx;
86         struct cookie *cookie;
87
88         // If application add a handle let's free it now
89         assert (session->values != NULL);
90
91         // Free session handle with a standard Free function, with app callback or ignore it
92         for (idx=0; idx < sessions.apicount; idx ++)
93                 afb_session_set_value(session, idx, NULL, NULL);
94
95         // free cookies
96         cookie = session->cookies;
97         while (cookie != NULL) {
98                 session->cookies = cookie->next;
99                 if (cookie->value != NULL && cookie->freecb != NULL)
100                         cookie->freecb(cookie->value);
101                 free(cookie);
102                 cookie = session->cookies;
103         }
104 }
105
106 // Create a new store in RAM, not that is too small it will be automatically extended
107 void afb_session_init (int max_session_count, int timeout, const char *initok, int context_count)
108 {
109         // let's create as store as hashtable does not have any
110         sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct afb_session));
111         sessions.max = max_session_count;
112         sessions.timeout = timeout;
113         sessions.apicount = context_count;
114         if (initok == NULL)
115                 /* without token, a secret is made to forbid creation of sessions */
116                 new_uuid(sessions.initok);
117         else if (strlen(initok) < sizeof(sessions.store[0]->token))
118                 strcpy(sessions.initok, initok);
119         else {
120                 ERROR("initial token '%s' too long (max length 36)", initok);
121                 exit(1);
122         }
123 }
124
125 static struct afb_session *search (const char* uuid)
126 {
127         int  idx;
128         struct afb_session *session;
129
130         assert (uuid != NULL);
131
132         pthread_mutex_lock(&sessions.mutex);
133
134         for (idx=0; idx < sessions.max; idx++) {
135                 session = sessions.store[idx];
136                 if (session && (0 == strcmp (uuid, session->uuid)))
137                         goto found;
138         }
139         session = NULL;
140
141 found:
142         pthread_mutex_unlock(&sessions.mutex);
143         return session;
144 }
145
146 static int destroy (struct afb_session *session)
147 {
148         int idx;
149         int status;
150
151         assert (session != NULL);
152
153         pthread_mutex_lock(&sessions.mutex);
154
155         for (idx=0; idx < sessions.max; idx++) {
156                 if (sessions.store[idx] == session) {
157                         sessions.store[idx] = NULL;
158                         sessions.count--;
159                         status = 1;
160                         goto deleted;
161                 }
162         }
163         status = 0;
164 deleted:
165         pthread_mutex_unlock(&sessions.mutex);
166         return status;
167 }
168
169 static int add (struct afb_session *session)
170 {
171         int idx;
172         int status;
173
174         assert (session != NULL);
175
176         pthread_mutex_lock(&sessions.mutex);
177
178         for (idx=0; idx < sessions.max; idx++) {
179                 if (NULL == sessions.store[idx]) {
180                         sessions.store[idx] = session;
181                         sessions.count++;
182                         status = 1;
183                         goto added;
184                 }
185         }
186         status = 0;
187 added:
188         pthread_mutex_unlock(&sessions.mutex);
189         return status;
190 }
191
192 // Check if context timeout or not
193 static int is_expired (struct afb_session *ctx, time_t now)
194 {
195         assert (ctx != NULL);
196         return ctx->expiration < now;
197 }
198
199 // Check if context is active or not
200 static int is_active (struct afb_session *ctx, time_t now)
201 {
202         assert (ctx != NULL);
203         return ctx->uuid[0] != 0 && ctx->expiration >= now;
204 }
205
206 // Loop on every entry and remove old context sessions.hash
207 static void cleanup (time_t now)
208 {
209         struct afb_session *ctx;
210         long idx;
211
212         // Loop on Sessions Table and remove anything that is older than timeout
213         for (idx=0; idx < sessions.max; idx++) {
214                 ctx = sessions.store[idx];
215                 if (ctx != NULL && is_expired(ctx, now)) {
216                         afb_session_close (ctx);
217                 }
218         }
219 }
220
221 static struct afb_session *make_session (const char *uuid, int timeout, time_t now)
222 {
223         struct afb_session *session;
224
225         /* allocates a new one */
226         session = calloc(1, sizeof(struct afb_session) + ((unsigned)sessions.apicount * sizeof(*session->values)));
227         if (session == NULL) {
228                 errno = ENOMEM;
229                 goto error;
230         }
231         session->values = (void*)(session + 1);
232
233         /* generate the uuid */
234         if (uuid == NULL) {
235                 new_uuid(session->uuid);
236         } else {
237                 if (strlen(uuid) >= sizeof session->uuid) {
238                         errno = EINVAL;
239                         goto error2;
240                 }
241                 strcpy(session->uuid, uuid);
242         }
243
244         /* init the token */
245         strcpy(session->token, sessions.initok);
246         session->timeout = timeout;
247         if (timeout != 0)
248                 session->expiration = now + timeout;
249         else {
250                 session->expiration = (time_t)(~(time_t)0);
251                 if (session->expiration < 0)
252                         session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1);
253         }
254         if (!add (session)) {
255                 errno = ENOMEM;
256                 goto error2;
257         }
258
259         session->access = now;
260         session->refcount = 1;
261         return session;
262
263 error2:
264         free(session);
265 error:
266         return NULL;
267 }
268
269 struct afb_session *afb_session_create (const char *uuid, int timeout)
270 {
271         time_t now;
272
273         /* cleaning */
274         now = NOW;
275         cleanup (now);
276
277         /* search for an existing one not too old */
278         if (uuid != NULL && search(uuid) != NULL) {
279                 errno = EEXIST;
280                 return NULL;
281         }
282
283         return make_session(uuid, timeout, now);
284 }
285
286 // This function will return exiting session or newly created session
287 struct afb_session *afb_session_get (const char *uuid, int *created)
288 {
289         struct afb_session *session;
290         time_t now;
291
292         /* cleaning */
293         now = NOW;
294         cleanup (now);
295
296         /* search for an existing one not too old */
297         if (uuid != NULL) {
298                 session = search(uuid);
299                 if (session != NULL) {
300                         *created = 0;
301                         session->access = now;
302                         session->refcount++;
303                         return session;
304                 }
305         }
306
307         *created = 1;
308         return make_session(uuid, sessions.timeout, now);
309 }
310
311 struct afb_session *afb_session_addref(struct afb_session *session)
312 {
313         if (session != NULL)
314                 session->refcount++;
315         return session;
316 }
317
318 void afb_session_unref(struct afb_session *session)
319 {
320         if (session != NULL) {
321                 assert(session->refcount != 0);
322                 --session->refcount;
323                 if (session->refcount == 0 && session->uuid[0] == 0) {
324                         destroy (session);
325                         free(session);
326                 }
327         }
328 }
329
330 // Free Client Session Context
331 void afb_session_close (struct afb_session *session)
332 {
333         assert(session != NULL);
334         if (session->uuid[0] != 0) {
335                 session->uuid[0] = 0;
336                 free_data (session);
337                 if (session->refcount == 0) {
338                         destroy (session);
339                         free(session);
340                 }
341         }
342 }
343
344 // Sample Generic Ping Debug API
345 int afb_session_check_token (struct afb_session *session, const char *token)
346 {
347         assert(session != NULL);
348         assert(token != NULL);
349
350         // compare current token with previous one
351         if (!is_active (session, NOW))
352                 return 0;
353
354         if (session->token[0] && strcmp (token, session->token) != 0)
355                 return 0;
356
357         return 1;
358 }
359
360 // generate a new token and update client context
361 void afb_session_new_token (struct afb_session *session)
362 {
363         assert(session != NULL);
364
365         // Old token was valid let's regenerate a new one
366         new_uuid(session->token);
367
368         // keep track of time for session timeout and further clean up
369         if (session->timeout != 0)
370                 session->expiration = NOW + session->timeout;
371 }
372
373 const char *afb_session_uuid (struct afb_session *session)
374 {
375         assert(session != NULL);
376         return session->uuid;
377 }
378
379 const char *afb_session_token (struct afb_session *session)
380 {
381         assert(session != NULL);
382         return session->token;
383 }
384
385 unsigned afb_session_get_LOA (struct afb_session *session)
386 {
387         assert(session != NULL);
388         return session->loa;
389 }
390
391 void afb_session_set_LOA (struct afb_session *session, unsigned loa)
392 {
393         assert(session != NULL);
394         session->loa = loa;
395 }
396
397 void *afb_session_get_value(struct afb_session *session, int index)
398 {
399         assert(session != NULL);
400         assert(index >= 0);
401         assert(index < sessions.apicount);
402         return session->values[index].value;
403 }
404
405 void afb_session_set_value(struct afb_session *session, int index, void *value, void (*freecb)(void*))
406 {
407         struct value prev;
408         assert(session != NULL);
409         assert(index >= 0);
410         assert(index < sessions.apicount);
411         prev = session->values[index];
412         session->values[index] = (struct value){.value = value, .freecb = freecb};
413         if (prev.value != NULL && prev.value != value && prev.freecb != NULL)
414                 prev.freecb(prev.value);
415 }
416
417 void *afb_session_get_cookie(struct afb_session *session, const void *key)
418 {
419         struct cookie *cookie;
420
421         cookie = session->cookies;
422         while(cookie != NULL) {
423                 if (cookie->key == key)
424                         return cookie->value;
425                 cookie = cookie->next;
426         }
427         return NULL;
428 }
429
430 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
431 {
432         struct cookie *cookie;
433
434         /* search for a replacement */
435         cookie = session->cookies;
436         while(cookie != NULL) {
437                 if (cookie->key == key) {
438                         if (cookie->value != NULL && cookie->value != value && cookie->freecb != NULL)
439                                 cookie->freecb(cookie->value);
440                         cookie->value = value;
441                         cookie->freecb = freecb;
442                         return 0;
443                 }
444                 cookie = cookie->next;
445         }
446
447         /* allocates */
448         cookie = malloc(sizeof *cookie);
449         if (cookie == NULL) {
450                 errno = ENOMEM;
451                 return -1;
452         }
453
454         cookie->key = key;
455         cookie->value = value;
456         cookie->freecb = freecb;
457         cookie->next = session->cookies;
458         session->cookies = cookie;
459         return 0;
460 }
461