afb-session: fix an over allocation
[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 COOKEYCOUNT  8
35 #define COOKEYMASK   (COOKEYCOUNT - 1)
36
37 #define NOW (time(NULL))
38
39 struct cookie
40 {
41         struct cookie *next;
42         const void *key;
43         void *value;
44         void (*freecb)(void*);
45 };
46
47 struct afb_session
48 {
49         unsigned refcount;
50         int timeout;
51         time_t expiration;    // expiration time of the token
52         time_t access;
53         pthread_mutex_t mutex;
54         char uuid[37];        // long term authentication of remote client
55         char token[37];       // short term authentication of remote client
56         struct cookie *cookies[COOKEYCOUNT];
57 };
58
59 // Session UUID are store in a simple array [for 10 sessions this should be enough]
60 static struct {
61         pthread_mutex_t mutex;          // declare a mutex to protect hash table
62         struct afb_session **store;          // sessions store
63         int count;                      // current number of sessions
64         int max;
65         int timeout;
66         char initok[37];
67 } sessions;
68
69 /**
70  * Get the index of the 'key' in the cookies array.
71  * @param key the key to scan
72  * @return the index of the list for key within cookies
73  */
74 static int cookeyidx(const void *key)
75 {
76         intptr_t x = (intptr_t)key;
77         unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
78         return r & COOKEYMASK;
79 }
80
81 /* generate a uuid */
82 static void new_uuid(char uuid[37])
83 {
84         uuid_t newuuid;
85         uuid_generate(newuuid);
86         uuid_unparse_lower(newuuid, uuid);
87 }
88
89 static inline void lock(struct afb_session *session)
90 {
91         pthread_mutex_lock(&session->mutex);
92 }
93
94 static inline void unlock(struct afb_session *session)
95 {
96         pthread_mutex_unlock(&session->mutex);
97 }
98
99 // Free context [XXXX Should be protected again memory abort XXXX]
100 static void free_data (struct afb_session *session)
101 {
102         int idx;
103         struct cookie *cookie, *next;
104
105         // free cookies
106         for (idx = 0 ; idx < COOKEYCOUNT ; idx++) {
107                 cookie = session->cookies[idx];
108                 session->cookies[idx] = NULL;
109                 while (cookie != NULL) {
110                         next = cookie->next;
111                         if (cookie->freecb != NULL)
112                                 cookie->freecb(cookie->value);
113                         free(cookie);
114                         cookie = next;
115                 }
116         }
117 }
118
119 // Create a new store in RAM, not that is too small it will be automatically extended
120 void afb_session_init (int max_session_count, int timeout, const char *initok)
121 {
122         // let's create as store as hashtable does not have any
123         sessions.store = calloc (1 + (unsigned)max_session_count, sizeof *sessions.store);
124         pthread_mutex_init(&sessions.mutex, NULL);
125         sessions.max = max_session_count;
126         sessions.timeout = timeout;
127         if (initok == NULL)
128                 /* without token, a secret is made to forbid creation of sessions */
129                 new_uuid(sessions.initok);
130         else if (strlen(initok) < sizeof(sessions.store[0]->token))
131                 strcpy(sessions.initok, initok);
132         else {
133                 ERROR("initial token '%s' too long (max length 36)", initok);
134                 exit(1);
135         }
136 }
137
138 static struct afb_session *search (const char* uuid)
139 {
140         int  idx;
141         struct afb_session *session;
142
143         assert (uuid != NULL);
144
145         pthread_mutex_lock(&sessions.mutex);
146
147         for (idx=0; idx < sessions.max; idx++) {
148                 session = sessions.store[idx];
149                 if (session && (0 == strcmp (uuid, session->uuid)))
150                         goto found;
151         }
152         session = NULL;
153
154 found:
155         pthread_mutex_unlock(&sessions.mutex);
156         return session;
157 }
158
159 static int destroy (struct afb_session *session)
160 {
161         int idx;
162         int status;
163
164         assert (session != NULL);
165
166         pthread_mutex_lock(&sessions.mutex);
167
168         for (idx=0; idx < sessions.max; idx++) {
169                 if (sessions.store[idx] == session) {
170                         sessions.store[idx] = NULL;
171                         sessions.count--;
172                         status = 1;
173                         goto deleted;
174                 }
175         }
176         status = 0;
177 deleted:
178         pthread_mutex_unlock(&sessions.mutex);
179         return status;
180 }
181
182 static int add (struct afb_session *session)
183 {
184         int idx;
185         int status;
186
187         assert (session != NULL);
188
189         pthread_mutex_lock(&sessions.mutex);
190
191         for (idx=0; idx < sessions.max; idx++) {
192                 if (NULL == sessions.store[idx]) {
193                         sessions.store[idx] = session;
194                         sessions.count++;
195                         status = 1;
196                         goto added;
197                 }
198         }
199         status = 0;
200 added:
201         pthread_mutex_unlock(&sessions.mutex);
202         return status;
203 }
204
205 // Check if context timeout or not
206 static int is_expired (struct afb_session *ctx, time_t now)
207 {
208         assert (ctx != NULL);
209         return ctx->expiration < now;
210 }
211
212 // Check if context is active or not
213 static int is_active (struct afb_session *ctx, time_t now)
214 {
215         assert (ctx != NULL);
216         return ctx->uuid[0] != 0 && ctx->expiration >= now;
217 }
218
219 // Loop on every entry and remove old context sessions.hash
220 static void cleanup (time_t now)
221 {
222         struct afb_session *ctx;
223         long idx;
224
225         // Loop on Sessions Table and remove anything that is older than timeout
226         for (idx=0; idx < sessions.max; idx++) {
227                 ctx = sessions.store[idx];
228                 if (ctx != NULL && is_expired(ctx, now)) {
229                         afb_session_close (ctx);
230                 }
231         }
232 }
233
234 static struct afb_session *make_session (const char *uuid, int timeout, time_t now)
235 {
236         struct afb_session *session;
237
238         /* allocates a new one */
239         session = calloc(1, sizeof *session);
240         if (session == NULL) {
241                 errno = ENOMEM;
242                 goto error;
243         }
244         pthread_mutex_init(&session->mutex, NULL);
245
246         /* generate the uuid */
247         if (uuid == NULL) {
248                 new_uuid(session->uuid);
249         } else {
250                 if (strlen(uuid) >= sizeof session->uuid) {
251                         errno = EINVAL;
252                         goto error2;
253                 }
254                 strcpy(session->uuid, uuid);
255         }
256
257         /* init the token */
258         strcpy(session->token, sessions.initok);
259         session->timeout = timeout;
260         if (timeout != 0)
261                 session->expiration = now + timeout;
262         else {
263                 session->expiration = (time_t)(~(time_t)0);
264                 if (session->expiration < 0)
265                         session->expiration = (time_t)(((unsigned long long)session->expiration) >> 1);
266         }
267         if (!add (session)) {
268                 errno = ENOMEM;
269                 goto error2;
270         }
271
272         session->access = now;
273         session->refcount = 1;
274         return session;
275
276 error2:
277         free(session);
278 error:
279         return NULL;
280 }
281
282 struct afb_session *afb_session_create (const char *uuid, int timeout)
283 {
284         time_t now;
285
286         /* cleaning */
287         now = NOW;
288         cleanup (now);
289
290         /* search for an existing one not too old */
291         if (uuid != NULL && search(uuid) != NULL) {
292                 errno = EEXIST;
293                 return NULL;
294         }
295
296         return make_session(uuid, timeout, now);
297 }
298
299 // This function will return exiting session or newly created session
300 struct afb_session *afb_session_get (const char *uuid, int *created)
301 {
302         struct afb_session *session;
303         time_t now;
304
305         /* cleaning */
306         now = NOW;
307         cleanup (now);
308
309         /* search for an existing one not too old */
310         if (uuid != NULL) {
311                 session = search(uuid);
312                 if (!created)
313                         return session;
314                 if (session != NULL) {
315                         *created = 0;
316                         session->access = now;
317                         session->refcount++;
318                         return session;
319                 }
320         }
321
322         if (created)
323                 *created = 1;
324
325         return make_session(uuid, sessions.timeout, now);
326 }
327
328 struct afb_session *afb_session_addref(struct afb_session *session)
329 {
330         if (session != NULL)
331                 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
332         return session;
333 }
334
335 void afb_session_unref(struct afb_session *session)
336 {
337         if (session != NULL) {
338                 assert(session->refcount != 0);
339                 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
340                         if (session->uuid[0] == 0) {
341                                 destroy (session);
342                                 pthread_mutex_destroy(&session->mutex);
343                                 free(session);
344                         }
345                 }
346         }
347 }
348
349 // Free Client Session Context
350 void afb_session_close (struct afb_session *session)
351 {
352         assert(session != NULL);
353         if (session->uuid[0] != 0) {
354                 session->uuid[0] = 0;
355                 free_data (session);
356                 if (session->refcount == 0) {
357                         destroy (session);
358                         free(session);
359                 }
360         }
361 }
362
363 // Sample Generic Ping Debug API
364 int afb_session_check_token (struct afb_session *session, const char *token)
365 {
366         assert(session != NULL);
367         assert(token != NULL);
368
369         // compare current token with previous one
370         if (!is_active (session, NOW))
371                 return 0;
372
373         if (session->token[0] && strcmp (token, session->token) != 0)
374                 return 0;
375
376         return 1;
377 }
378
379 // generate a new token and update client context
380 void afb_session_new_token (struct afb_session *session)
381 {
382         assert(session != NULL);
383
384         // Old token was valid let's regenerate a new one
385         new_uuid(session->token);
386
387         // keep track of time for session timeout and further clean up
388         if (session->timeout != 0)
389                 session->expiration = NOW + session->timeout;
390 }
391
392 const char *afb_session_uuid (struct afb_session *session)
393 {
394         assert(session != NULL);
395         return session->uuid;
396 }
397
398 const char *afb_session_token (struct afb_session *session)
399 {
400         assert(session != NULL);
401         return session->token;
402 }
403
404 static struct cookie *cookie_search(struct afb_session *session, const void *key, int *idx)
405 {
406         struct cookie *cookie;
407
408         cookie = session->cookies[*idx = cookeyidx(key)];
409         while(cookie != NULL && cookie->key != key)
410                 cookie = cookie->next;
411         return cookie;
412 }
413
414 static struct cookie *cookie_add(struct afb_session *session, int idx, const void *key, void *value, void (*freecb)(void*))
415 {
416         struct cookie *cookie;
417
418         cookie = malloc(sizeof *cookie);
419         if (!cookie)
420                 errno = ENOMEM;
421         else {
422                 cookie->key = key;
423                 cookie->value = value;
424                 cookie->freecb = freecb;
425                 cookie->next = session->cookies[idx];
426                 session->cookies[idx] = cookie;
427         }
428         return cookie;
429 }
430
431 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void), void (*freecb)(void*))
432 {
433         int idx;
434         void *value;
435         struct cookie *cookie;
436
437         lock(session);
438         cookie = cookie_search(session, key, &idx);
439         if (cookie)
440                 value = cookie->value;
441         else {
442                 value = makecb ? makecb() : NULL;
443                 if (makecb || freecb) {
444                         cookie = cookie_add(session, idx, key, value, freecb);
445                         if (!cookie) {
446                                 if (makecb && freecb)
447                                         free(value);
448                                 value = NULL;
449                         }
450                 }
451         }
452         unlock(session);
453         return value;
454 }
455
456 void *afb_session_get_cookie(struct afb_session *session, const void *key)
457 {
458         int idx;
459         void *value;
460         struct cookie *cookie;
461
462         lock(session);
463         cookie = cookie_search(session, key, &idx);
464         value = cookie ? cookie->value : NULL;
465         unlock(session);
466         return value;
467 }
468
469 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
470 {
471         int idx;
472         struct cookie *cookie;
473
474         lock(session);
475         cookie = cookie_search(session, key, &idx);
476         if (!cookie)
477                 cookie = cookie_add(session, idx, key, value, freecb);
478         else {
479                 if (cookie->value != value && cookie->freecb)
480                         cookie->freecb(cookie->value);
481                 cookie->value = value;
482                 cookie->freecb = freecb;
483         }
484         unlock(session);
485         return -!cookie;
486 }
487