Move tokens from sessions to requests
[src/app-framework-binder.git] / src / afb-session.c
1 /*
2  * Copyright (C) 2015-2019 "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 <stdint.h>
25 #include <limits.h>
26 #include <string.h>
27 #include <errno.h>
28
29 #include "afb-session.h"
30 #include "afb-hook.h"
31 #include "afb-token.h"
32 #include "verbose.h"
33 #include "pearson.h"
34 #include "uuid.h"
35
36 #define HEADCOUNT       16
37 #define COOKIECOUNT     8
38 #define COOKIEMASK      (COOKIECOUNT - 1)
39
40 #define _MAXEXP_        ((time_t)(~(time_t)0))
41 #define _MAXEXP2_       ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
42 #define MAX_EXPIRATION  (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
43 #define NOW             (time_now())
44
45 /**
46  * structure for a cookie added to sessions
47  */
48 struct cookie
49 {
50         struct cookie *next;    /**< link to next cookie */
51         const void *key;        /**< pointer key */
52         void *value;            /**< value */
53         void (*freecb)(void*);  /**< function to call when session is closed */
54 };
55
56 /**
57  * structure for session
58  */
59 struct afb_session
60 {
61         struct afb_session *next; /**< link to the next */
62         unsigned refcount;      /**< count of reference to the session */
63         int timeout;            /**< timeout of the session */
64         time_t expiration;      /**< expiration time of the session */
65         pthread_mutex_t mutex;  /**< mutex of the session */
66         struct cookie *cookies[COOKIECOUNT]; /**< cookies of the session */
67         char *lang;             /**< current language setting for the session */
68         uint8_t closed: 1;      /**< is the session closed ? */
69         uint8_t autoclose: 1;   /**< close the session when unreferenced */
70         uint8_t notinset: 1;    /**< session removed from the set of sessions */
71         uuid_stringz_t uuid;    /**< indentification of client seesion */
72 };
73
74 /**
75  * structure for managing sessions
76  */
77 static struct {
78         int count;              /**< current number of sessions */
79         int max;                /**< maximum count of sessions */
80         int timeout;            /**< common initial timeout */
81         struct afb_session *heads[HEADCOUNT]; /**< sessions */
82         struct afb_token *initok;/**< common initial token */
83         pthread_mutex_t mutex;  /**< declare a mutex to protect hash table */
84 } sessions = {
85         .count = 0,
86         .max = 10,
87         .timeout = 3600,
88         .heads = { 0 },
89         .initok = 0,
90         .mutex = PTHREAD_MUTEX_INITIALIZER
91 };
92
93 /**
94  * Get the actual raw time
95  */
96 static inline time_t time_now()
97 {
98         struct timespec ts;
99         clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
100         return ts.tv_sec;
101 }
102
103 /* lock the set of sessions for exclusive access */
104 static inline void sessionset_lock()
105 {
106         pthread_mutex_lock(&sessions.mutex);
107 }
108
109 /* unlock the set of sessions of exclusive access */
110 static inline void sessionset_unlock()
111 {
112         pthread_mutex_unlock(&sessions.mutex);
113 }
114
115 /*
116  * search within the set of sessions the session of 'uuid'.
117  * 'hashidx' is the precomputed hash for 'uuid'
118  * return the session or NULL
119  */
120 static struct afb_session *sessionset_search(const char *uuid, uint8_t hashidx)
121 {
122         struct afb_session *session;
123
124         session = sessions.heads[hashidx];
125         while (session && strcmp(uuid, session->uuid))
126                 session = session->next;
127
128         return session;
129 }
130
131 /* add 'session' to the set of sessions */
132 static int sessionset_add(struct afb_session *session, uint8_t hashidx)
133 {
134         /* check availability */
135         if (sessions.max && sessions.count >= sessions.max) {
136                 errno = EBUSY;
137                 return -1;
138         }
139
140         /* add the session */
141         session->next = sessions.heads[hashidx];
142         sessions.heads[hashidx] = session;
143         sessions.count++;
144         return 0;
145 }
146
147 /* make a new uuid not used in the set of sessions */
148 static uint8_t sessionset_make_uuid (uuid_stringz_t uuid)
149 {
150         uint8_t hashidx;
151
152         do {
153                 uuid_new_stringz(uuid);
154                 hashidx = pearson4(uuid);
155         } while(sessionset_search(uuid, hashidx));
156         return hashidx;
157 }
158
159 /* lock the 'session' for exclusive access */
160 static inline void session_lock(struct afb_session *session)
161 {
162         pthread_mutex_lock(&session->mutex);
163 }
164
165 /* unlock the 'session' of exclusive access */
166 static inline void session_unlock(struct afb_session *session)
167 {
168         pthread_mutex_unlock(&session->mutex);
169 }
170
171 /* close the 'session' */
172 static void session_close(struct afb_session *session)
173 {
174         int idx;
175         struct cookie *cookie;
176
177         /* close only one time */
178         if (!session->closed) {
179                 /* close it now */
180                 session->closed = 1;
181
182 #if WITH_AFB_HOOK
183                 /* emit the hook */
184                 afb_hook_session_close(session);
185 #endif
186
187                 /* release cookies */
188                 for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
189                         while ((cookie = session->cookies[idx])) {
190                                 session->cookies[idx] = cookie->next;
191                                 if (cookie->freecb != NULL)
192                                         cookie->freecb(cookie->value);
193                                 free(cookie);
194                         }
195                 }
196         }
197 }
198
199 /* destroy the 'session' */
200 static void session_destroy (struct afb_session *session)
201 {
202 #if WITH_AFB_HOOK
203         afb_hook_session_destroy(session);
204 #endif
205         pthread_mutex_destroy(&session->mutex);
206         free(session->lang);
207         free(session);
208 }
209
210 /* update expiration of 'session' according to 'now' */
211 static void session_update_expiration(struct afb_session *session, time_t now)
212 {
213         time_t expiration;
214
215         /* compute expiration */
216         expiration = now + afb_session_timeout(session);
217         if (expiration < 0)
218                 expiration = MAX_EXPIRATION;
219
220         /* record the expiration */
221         session->expiration = expiration;
222 }
223
224 /*
225  * Add a new session with the 'uuid' (of 'hashidx')
226  * and the 'timeout' starting from 'now'.
227  * Add it to the set of sessions
228  * Return the created session
229  */
230 static struct afb_session *session_add(const char *uuid, int timeout, time_t now, uint8_t hashidx)
231 {
232         struct afb_session *session;
233
234         /* check arguments */
235         if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
236          || (uuid && strlen(uuid) >= sizeof session->uuid)) {
237                 errno = EINVAL;
238                 return NULL;
239         }
240
241         /* allocates a new one */
242         session = calloc(1, sizeof *session);
243         if (session == NULL) {
244                 errno = ENOMEM;
245                 return NULL;
246         }
247
248         /* initialize */
249         pthread_mutex_init(&session->mutex, NULL);
250         session->refcount = 1;
251         strcpy(session->uuid, uuid);
252         session->timeout = timeout;
253         session_update_expiration(session, now);
254
255         /* add */
256         if (sessionset_add(session, hashidx)) {
257                 free(session);
258                 return NULL;
259         }
260
261 #if WITH_AFB_HOOK
262         afb_hook_session_create(session);
263 #endif
264
265         return session;
266 }
267
268 /* Remove expired sessions and return current time (now) */
269 static time_t sessionset_cleanup (int force)
270 {
271         struct afb_session *session, **prv;
272         int idx;
273         time_t now;
274
275         /* Loop on Sessions Table and remove anything that is older than timeout */
276         now = NOW;
277         for (idx = 0 ; idx < HEADCOUNT; idx++) {
278                 prv = &sessions.heads[idx];
279                 while ((session = *prv)) {
280                         session_lock(session);
281                         if (force || session->expiration < now)
282                                 session_close(session);
283                         if (!session->closed)
284                                 prv = &session->next;
285                         else {
286                                 *prv = session->next;
287                                 sessions.count--;
288                                 session->notinset = 1;
289                                 if ( !session->refcount) {
290                                         session_destroy(session);
291                                         continue;
292                                 }
293                         }
294                         session_unlock(session);
295                 }
296         }
297         return now;
298 }
299
300 /**
301  * Initialize the session manager with a 'max_session_count',
302  * an initial common 'timeout' and an initial common token 'initok'.
303  *
304  * @param max_session_count  maximum allowed session count in the same time
305  * @param timeout            the initial default timeout of sessions
306  * @param initok             the initial default token of sessions
307  */
308 int afb_session_init (int max_session_count, int timeout, const char *initok)
309 {
310         int rc;
311         uuid_stringz_t uuid;
312
313         /* check parameters */
314         if (initok && strlen(initok) >= sizeof sessions.initok) {
315                 ERROR("initial token '%s' too long (max length %d)",
316                         initok, ((int)(sizeof sessions.initok)) - 1);
317                 errno = EINVAL;
318                 return -1;
319         }
320
321         /* init the sessionset (after cleanup) */
322         sessionset_lock();
323         sessionset_cleanup(1);
324         sessions.max = max_session_count;
325         sessions.timeout = timeout;
326         if (initok == NULL) {
327                 uuid_new_stringz(uuid);
328                 initok = uuid;
329         }
330         sessions.initok = 0;
331         if (*initok) {
332                 rc = afb_token_get(&sessions.initok, initok);
333                 if (rc < 0)
334                         return rc;
335         }
336         sessionset_unlock();
337         return 0;
338 }
339
340 /**
341  * Iterate the sessions and call 'callback' with
342  * the 'closure' for each session.
343  */
344 void afb_session_foreach(void (*callback)(void *closure, struct afb_session *session), void *closure)
345 {
346         struct afb_session *session;
347         int idx;
348
349         /* Loop on Sessions Table and remove anything that is older than timeout */
350         sessionset_lock();
351         for (idx = 0 ; idx < HEADCOUNT; idx++) {
352                 session = sessions.heads[idx];
353                 while (session) {
354                         if (!session->closed)
355                                 callback(closure, session);
356                         session = session->next;
357                 }
358         }
359         sessionset_unlock();
360 }
361
362 /**
363  * Cleanup the sessionset of its closed or expired sessions
364  */
365 void afb_session_purge()
366 {
367         sessionset_lock();
368         sessionset_cleanup(0);
369         sessionset_unlock();
370 }
371
372 /**
373  * @return the initial token set at initialization
374  */
375 const char *afb_session_initial_token()
376 {
377         return sessions.initok ? afb_token_string(sessions.initok) : "";
378 }
379
380 /* Searchs the session of 'uuid' */
381 struct afb_session *afb_session_search (const char *uuid)
382 {
383         struct afb_session *session;
384
385         sessionset_lock();
386         sessionset_cleanup(0);
387         session = sessionset_search(uuid, pearson4(uuid));
388         session = afb_session_addref(session);
389         sessionset_unlock();
390         return session;
391
392 }
393
394 /**
395  * Creates a new session with 'timeout'
396  */
397 struct afb_session *afb_session_create (int timeout)
398 {
399         return afb_session_get(NULL, timeout, NULL);
400 }
401
402 /**
403  * Returns the timeout of 'session' in seconds
404  */
405 int afb_session_timeout(struct afb_session *session)
406 {
407         int timeout;
408
409         /* compute timeout */
410         timeout = session->timeout;
411         if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
412                 timeout = sessions.timeout;
413         if (timeout < 0)
414                 timeout = INT_MAX;
415         return timeout;
416 }
417
418 /**
419  * Returns the second remaining before expiration of 'session'
420  */
421 int afb_session_what_remains(struct afb_session *session)
422 {
423         return (int)(session->expiration - NOW);
424 }
425
426 /* This function will return exiting session or newly created session */
427 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
428 {
429         uuid_stringz_t _uuid_;
430         uint8_t hashidx;
431         struct afb_session *session;
432         time_t now;
433         int c;
434
435         /* cleaning */
436         sessionset_lock();
437         now = sessionset_cleanup(0);
438
439         /* search for an existing one not too old */
440         if (!uuid) {
441                 hashidx = sessionset_make_uuid(_uuid_);
442                 uuid = _uuid_;
443         } else {
444                 hashidx = pearson4(uuid);
445                 session = sessionset_search(uuid, hashidx);
446                 if (session) {
447                         /* session found */
448                         afb_session_addref(session);
449                         c = 0;
450                         goto end;
451                 }
452         }
453         /* create the session */
454         session = session_add(uuid, timeout, now, hashidx);
455         c = 1;
456 end:
457         sessionset_unlock();
458         if (created)
459                 *created = c;
460
461         return session;
462 }
463
464 /* increase the use count on 'session' (can be NULL) */
465 struct afb_session *afb_session_addref(struct afb_session *session)
466 {
467         if (session != NULL) {
468 #if WITH_AFB_HOOK
469                 afb_hook_session_addref(session);
470 #endif
471                 session_lock(session);
472                 session->refcount++;
473                 session_unlock(session);
474         }
475         return session;
476 }
477
478 /* decrease the use count of 'session' (can be NULL) */
479 void afb_session_unref(struct afb_session *session)
480 {
481         if (session == NULL)
482                 return;
483
484 #if WITH_AFB_HOOK
485         afb_hook_session_unref(session);
486 #endif
487         session_lock(session);
488         if (!--session->refcount) {
489                 if (session->autoclose)
490                         session_close(session);
491                 if (session->notinset) {
492                         session_destroy(session);
493                         return;
494                 }
495         }
496         session_unlock(session);
497 }
498
499 /* close 'session' */
500 void afb_session_close (struct afb_session *session)
501 {
502         session_lock(session);
503         session_close(session);
504         session_unlock(session);
505 }
506
507 /**
508  * Set the 'autoclose' flag of the 'session'
509  *
510  * A session whose autoclose flag is true will close as
511  * soon as it is no more referenced.
512  *
513  * @param session    the session to set
514  * @param autoclose  the value to set
515  */
516 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
517 {
518         session->autoclose = !!autoclose;
519 }
520
521 /* is 'session' closed? */
522 int afb_session_is_closed (struct afb_session *session)
523 {
524         return session->closed;
525 }
526
527 /* Returns the uuid of 'session' */
528 const char *afb_session_uuid (struct afb_session *session)
529 {
530         return session->uuid;
531 }
532
533 /**
534  * Get the index of the 'key' in the cookies array.
535  * @param key the key to scan
536  * @return the index of the list for key within cookies
537  */
538 static int cookeyidx(const void *key)
539 {
540         intptr_t x = (intptr_t)key;
541         unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
542         return r & COOKIEMASK;
543 }
544
545 /**
546  * Set, get, replace, remove a cookie of 'key' for the 'session'
547  *
548  * The behaviour of this function depends on its parameters:
549  *
550  * @param session       the session
551  * @param key           the key of the cookie
552  * @param makecb        the creation function or NULL
553  * @param freecb        the release function or NULL
554  * @param closure       an argument for makecb or the value if makecb==NULL
555  * @param replace       a boolean enforcing replacement of the previous value
556  *
557  * @return the value of the cookie
558  *
559  * The 'key' is a pointer and compared as pointers.
560  *
561  * For getting the current value of the cookie:
562  *
563  *   afb_session_cookie(session, key, NULL, NULL, NULL, 0)
564  *
565  * For storing the value of the cookie
566  *
567  *   afb_session_cookie(session, key, NULL, NULL, value, 1)
568  */
569 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
570 {
571         int idx;
572         void *value;
573         struct cookie *cookie, **prv;
574
575         /* get key hashed index */
576         idx = cookeyidx(key);
577
578         /* lock session and search for the cookie of 'key' */
579         session_lock(session);
580         prv = &session->cookies[idx];
581         for (;;) {
582                 cookie = *prv;
583                 if (!cookie) {
584                         /* 'key' not found, create value using 'closure' and 'makecb' */
585                         value = makecb ? makecb(closure) : closure;
586                         /* store the the only if it has some meaning */
587                         if (replace || makecb || freecb) {
588                                 cookie = malloc(sizeof *cookie);
589                                 if (!cookie) {
590                                         errno = ENOMEM;
591                                         /* calling freecb if there is no makecb may have issue */
592                                         if (makecb && freecb)
593                                                 freecb(value);
594                                         value = NULL;
595                                 } else {
596                                         cookie->key = key;
597                                         cookie->value = value;
598                                         cookie->freecb = freecb;
599                                         cookie->next = NULL;
600                                         *prv = cookie;
601                                 }
602                         }
603                         break;
604                 } else if (cookie->key == key) {
605                         /* cookie of key found */
606                         if (!replace)
607                                 /* not replacing, get the value */
608                                 value = cookie->value;
609                         else {
610                                 /* create value using 'closure' and 'makecb' */
611                                 value = makecb ? makecb(closure) : closure;
612
613                                 /* free previous value is needed */
614                                 if (cookie->value != value && cookie->freecb)
615                                         cookie->freecb(cookie->value);
616
617                                 /* if both value and freecb are NULL drop the cookie */
618                                 if (!value && !freecb) {
619                                         *prv = cookie->next;
620                                         free(cookie);
621                                 } else {
622                                         /* store the value and its releaser */
623                                         cookie->value = value;
624                                         cookie->freecb = freecb;
625                                 }
626                         }
627                         break;
628                 } else {
629                         prv = &(cookie->next);
630                 }
631         }
632
633         /* unlock the session and return the value */
634         session_unlock(session);
635         return value;
636 }
637
638 /**
639  * Get the cookie of 'key' in the 'session'.
640  *
641  * @param session  the session to search in
642  * @param key      the key of the data to retrieve
643  *
644  * @return the data staored for the key or NULL if the key isn't found
645  */
646 void *afb_session_get_cookie(struct afb_session *session, const void *key)
647 {
648         return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
649 }
650
651 /**
652  * Set the cookie of 'key' in the 'session' to the 'value' that can be
653  * cleaned using 'freecb' (if not null).
654  *
655  * @param session  the session to set
656  * @param key      the key of the data to store
657  * @param value    the value to store at key
658  * @param freecb   a function to use when the cookie value is to remove (or null)
659  *
660  * @return 0 in case of success or -1 in case of error
661  */
662 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
663 {
664         return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
665 }
666
667 /**
668  * Set the language attached to the session
669  *
670  * @param session the session to set
671  * @param lang    the language specifiction to set to session
672  *
673  * @return 0 in case of success or -1 in case of error
674  */
675 int afb_session_set_language(struct afb_session *session, const char *lang)
676 {
677         char *oldl, *newl;
678
679         newl = strdup(lang);
680         if (newl == NULL)
681                 return -1;
682
683         oldl = session->lang;
684         session->lang = newl;
685         free(oldl);
686         return 0;
687 }
688
689 /**
690  * Get the language attached to the session
691  *
692  * @param session the session to query
693  * @param lang    a default language specifiction
694  *
695  * @return the langauage specification to use for session
696  */
697 const char *afb_session_get_language(struct afb_session *session, const char *lang)
698 {
699         return session->lang ?: lang;
700 }