afb-session: Fix a typo
[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 SIZEUUID        37
35 #define HEADCOUNT       16
36 #define COOKIECOUNT     8
37 #define COOKIEMASK      (COOKIECOUNT - 1)
38
39 #define _MAXEXP_        ((time_t)(~(time_t)0))
40 #define _MAXEXP2_       ((time_t)((((unsigned long long)_MAXEXP_) >> 1)))
41 #define MAX_EXPIRATION  (_MAXEXP_ >= 0 ? _MAXEXP_ : _MAXEXP2_)
42 #define NOW             (time(NULL))
43
44 struct cookie
45 {
46         struct cookie *next;
47         const void *key;
48         void *value;
49         void (*freecb)(void*);
50 };
51
52 struct afb_session
53 {
54         struct afb_session *next; /* link to the next */
55         unsigned refcount;
56         int timeout;
57         time_t expiration;      // expiration time of the token
58         pthread_mutex_t mutex;
59         struct cookie *cookies[COOKIECOUNT];
60         char autoclose;
61         char idx;
62         char uuid[SIZEUUID];    // long term authentication of remote client
63         char token[SIZEUUID];   // short term authentication of remote client
64 };
65
66 // Session UUID are store in a simple array [for 10 sessions this should be enough]
67 static struct {
68         pthread_mutex_t mutex;  // declare a mutex to protect hash table
69         struct afb_session *heads[HEADCOUNT]; // sessions
70         int count;      // current number of sessions
71         int max;
72         int timeout;
73         char initok[SIZEUUID];
74 } sessions;
75
76 /* generate a uuid */
77 static void new_uuid(char uuid[SIZEUUID])
78 {
79         uuid_t newuuid;
80         uuid_generate(newuuid);
81         uuid_unparse_lower(newuuid, uuid);
82 }
83
84 static inline void lock(struct afb_session *session)
85 {
86         pthread_mutex_lock(&session->mutex);
87 }
88
89 static inline void unlock(struct afb_session *session)
90 {
91         pthread_mutex_unlock(&session->mutex);
92 }
93
94 // Free context [XXXX Should be protected again memory abort XXXX]
95 static void close_session(struct afb_session *session)
96 {
97         int idx;
98         struct cookie *cookie;
99
100         /* free cookies */
101         for (idx = 0 ; idx < COOKIECOUNT ; idx++) {
102                 while ((cookie = session->cookies[idx])) {
103                         session->cookies[idx] = cookie->next;
104                         if (cookie->freecb != NULL)
105                                 cookie->freecb(cookie->value);
106                         free(cookie);
107                 }
108         }
109 }
110
111 /* tiny hash function inspired from pearson */
112 static int pearson4(const char *text)
113 {
114         static uint8_t T[16] = {
115                  4,  1,  6,  0,  9, 14, 11,  5,
116                  2,  3, 12, 15, 10,  7,  8, 13
117         };
118         uint8_t r, c;
119
120         for (r = 0; (c = (uint8_t)*text) ; text++) {
121                 r = T[r ^ (15 & c)];
122                 r = T[r ^ (c >> 4)];
123         }
124         return r; // % HEADCOUNT;
125 }
126
127 // Create a new store in RAM, not that is too small it will be automatically extended
128 void afb_session_init (int max_session_count, int timeout, const char *initok)
129 {
130         pthread_mutex_init(&sessions.mutex, NULL);
131         sessions.max = max_session_count;
132         sessions.timeout = timeout;
133         if (initok == NULL)
134                 /* without token, a secret is made to forbid creation of sessions */
135                 new_uuid(sessions.initok);
136         else if (strlen(initok) < sizeof sessions.initok)
137                 strcpy(sessions.initok, initok);
138         else {
139                 ERROR("initial token '%s' too long (max length %d)", initok, ((int)(sizeof sessions.initok)) - 1);
140                 exit(1);
141         }
142 }
143
144 const char *afb_session_initial_token()
145 {
146         return sessions.initok;
147 }
148
149 static struct afb_session *search (const char* uuid, int idx)
150 {
151         struct afb_session *session;
152
153         session = sessions.heads[idx];
154         while (session && strcmp(uuid, session->uuid))
155                 session = session->next;
156
157         return session;
158 }
159
160 static void destroy (struct afb_session *session)
161 {
162         struct afb_session **prv;
163
164         assert (session != NULL);
165
166         close_session(session);
167         pthread_mutex_lock(&sessions.mutex);
168         prv = &sessions.heads[(int)session->idx];
169         while (*prv)
170                 if (*prv != session)
171                         prv = &((*prv)->next);
172                 else {
173                         *prv = session->next;
174                         sessions.count--;
175                         pthread_mutex_destroy(&session->mutex);
176                         free(session);
177                         break;
178                 }
179         pthread_mutex_unlock(&sessions.mutex);
180 }
181
182 // Loop on every entry and remove old context sessions.hash
183 static time_t cleanup ()
184 {
185         struct afb_session *session, *next;
186         int idx;
187         time_t now;
188
189         // Loop on Sessions Table and remove anything that is older than timeout
190         now = NOW;
191         for (idx = 0 ; idx < HEADCOUNT; idx++) {
192                 session = sessions.heads[idx];
193                 while (session) {
194                         next = session->next;
195                         if (session->expiration < now)
196                                 afb_session_close(session);
197                         session = next;
198                 }
199         }
200         return now;
201 }
202
203 static void update_timeout(struct afb_session *session, time_t now, int timeout)
204 {
205         time_t expiration;
206
207         /* compute expiration */
208         if (timeout == AFB_SESSION_TIMEOUT_INFINITE)
209                 expiration = MAX_EXPIRATION;
210         else {
211                 if (timeout == AFB_SESSION_TIMEOUT_DEFAULT)
212                         expiration = now + sessions.timeout;
213                 else
214                         expiration = now + timeout;
215                 if (expiration < 0)
216                         expiration = MAX_EXPIRATION;
217         }
218
219         /* record the values */
220         session->timeout = timeout;
221         session->expiration = expiration;
222 }
223
224 static void update_expiration(struct afb_session *session, time_t now)
225 {
226         update_timeout(session, now, session->timeout);
227 }
228
229 static struct afb_session *add_session (const char *uuid, int timeout, time_t now, int idx)
230 {
231         struct afb_session *session;
232
233         /* check arguments */
234         if (!AFB_SESSION_TIMEOUT_IS_VALID(timeout)
235          || (uuid && strlen(uuid) >= sizeof session->uuid)) {
236                 errno = EINVAL;
237                 return NULL;
238         }
239
240         /* check session count */
241         if (sessions.count >= sessions.max) {
242                 errno = EBUSY;
243                 return NULL;
244         }
245
246         /* allocates a new one */
247         session = calloc(1, sizeof *session);
248         if (session == NULL) {
249                 errno = ENOMEM;
250                 return NULL;
251         }
252
253         /* initialize */
254         pthread_mutex_init(&session->mutex, NULL);
255         session->refcount = 1;
256         strcpy(session->uuid, uuid);
257         strcpy(session->token, sessions.initok);
258         update_timeout(session, now, timeout);
259
260         /* link */
261         session->idx = (char)idx;
262         session->next = sessions.heads[idx];
263         sessions.heads[idx] = session;
264         sessions.count++;
265
266         return session;
267 }
268
269 /* create a new session for the given timeout */
270 static struct afb_session *new_session (int timeout, time_t now)
271 {
272         int idx;
273         char uuid[SIZEUUID];
274
275         do {
276                 new_uuid(uuid);
277                 idx = pearson4(uuid);
278         } while(search(uuid, idx));
279         return add_session(uuid, timeout, now, idx);
280 }
281
282 /* Creates a new session with 'timeout' */
283 struct afb_session *afb_session_create (int timeout)
284 {
285         time_t now;
286         struct afb_session *session;
287
288         /* cleaning */
289         pthread_mutex_lock(&sessions.mutex);
290         now = cleanup();
291         session = new_session(timeout, now);
292         pthread_mutex_unlock(&sessions.mutex);
293
294         return session;
295 }
296
297 /* Searchs the session of 'uuid' */
298 struct afb_session *afb_session_search (const char *uuid)
299 {
300         struct afb_session *session;
301
302         /* cleaning */
303         pthread_mutex_lock(&sessions.mutex);
304         cleanup();
305         session = search(uuid, pearson4(uuid));
306         if (session)
307                 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
308         pthread_mutex_unlock(&sessions.mutex);
309         return session;
310
311 }
312
313 /* This function will return exiting session or newly created session */
314 struct afb_session *afb_session_get (const char *uuid, int timeout, int *created)
315 {
316         int idx;
317         struct afb_session *session;
318         time_t now;
319
320         /* cleaning */
321         pthread_mutex_lock(&sessions.mutex);
322         now = cleanup();
323
324         /* search for an existing one not too old */
325         if (!uuid)
326                 session = new_session(timeout, now);
327         else {
328                 idx = pearson4(uuid);
329                 session = search(uuid, idx);
330                 if (session) {
331                         __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
332                         pthread_mutex_unlock(&sessions.mutex);
333                         if (created)
334                                 *created = 0;
335                         return session;
336                 }
337                 session = add_session (uuid, timeout, now, idx);
338         }
339         pthread_mutex_unlock(&sessions.mutex);
340
341         if (created)
342                 *created = !!session;
343
344         return session;
345 }
346
347 /* increase the use count on the session */
348 struct afb_session *afb_session_addref(struct afb_session *session)
349 {
350         if (session != NULL)
351                 __atomic_add_fetch(&session->refcount, 1, __ATOMIC_RELAXED);
352         return session;
353 }
354
355 /* decrease the use count of the session */
356 void afb_session_unref(struct afb_session *session)
357 {
358         if (session != NULL) {
359                 assert(session->refcount != 0);
360                 if (!__atomic_sub_fetch(&session->refcount, 1, __ATOMIC_RELAXED)) {
361                         pthread_mutex_lock(&session->mutex);
362                         if (session->autoclose || session->uuid[0] == 0)
363                                 destroy (session);
364                         else
365                                 pthread_mutex_unlock(&session->mutex);
366                 }
367         }
368 }
369
370 // close Client Session Context
371 void afb_session_close (struct afb_session *session)
372 {
373         assert(session != NULL);
374         pthread_mutex_lock(&session->mutex);
375         if (session->uuid[0] != 0) {
376                 session->uuid[0] = 0;
377                 if (session->refcount)
378                         close_session(session);
379                 else {
380                         destroy (session);
381                         return;
382                 }
383         }
384         pthread_mutex_unlock(&session->mutex);
385 }
386
387 /* set the autoclose flag */
388 void afb_session_set_autoclose(struct afb_session *session, int autoclose)
389 {
390         assert(session != NULL);
391         session->autoclose = (char)!!autoclose;
392 }
393
394 // is the session active?
395 int afb_session_is_active (struct afb_session *session)
396 {
397         assert(session != NULL);
398         return !!session->uuid[0];
399 }
400
401 // is the session closed?
402 int afb_session_is_closed (struct afb_session *session)
403 {
404         assert(session != NULL);
405         return !session->uuid[0];
406 }
407
408 // Sample Generic Ping Debug API
409 int afb_session_check_token (struct afb_session *session, const char *token)
410 {
411         assert(session != NULL);
412         assert(token != NULL);
413
414         if (!session->uuid[0])
415                 return 0;
416
417         if (session->expiration < NOW)
418                 return 0;
419
420         if (session->token[0] && strcmp (token, session->token) != 0)
421                 return 0;
422
423         return 1;
424 }
425
426 // generate a new token and update client context
427 void afb_session_new_token (struct afb_session *session)
428 {
429         assert(session != NULL);
430
431         // Old token was valid let's regenerate a new one
432         new_uuid(session->token);
433
434         // keep track of time for session timeout and further clean up
435         update_expiration(session, NOW);
436 }
437
438 /* Returns the uuid of 'session' */
439 const char *afb_session_uuid (struct afb_session *session)
440 {
441         assert(session != NULL);
442         return session->uuid;
443 }
444
445 /* Returns the token of 'session' */
446 const char *afb_session_token (struct afb_session *session)
447 {
448         assert(session != NULL);
449         return session->token;
450 }
451
452 /**
453  * Get the index of the 'key' in the cookies array.
454  * @param key the key to scan
455  * @return the index of the list for key within cookies
456  */
457 static int cookeyidx(const void *key)
458 {
459         intptr_t x = (intptr_t)key;
460         unsigned r = (unsigned)((x >> 5) ^ (x >> 15));
461         return r & COOKIEMASK;
462 }
463
464 /**
465  * Set, get, replace, remove a cookie of 'key' for the 'session'
466  *
467  * The behaviour of this function depends on its parameters:
468  *
469  * @param session       the session
470  * @param key           the key of the cookie
471  * @param makecb        the creation function or NULL
472  * @param freecb        the release function or NULL
473  * @param closure       an argument for makecb or the value if makecb==NULL
474  * @param replace       a boolean enforcing replecement of the previous value
475  *
476  * @return the value of the cookie
477  *
478  * The 'key' is a pointer and compared as pointers.
479  *
480  * For getting the current value of the cookie:
481  *
482  *   afb_session_cookie(session, key, NULL, NULL, NULL, 0)
483  *
484  * For storing the value of the cookie
485  *
486  *   afb_session_cookie(session, key, NULL, NULL, value, 1)
487  */
488 void *afb_session_cookie(struct afb_session *session, const void *key, void *(*makecb)(void *closure), void (*freecb)(void *item), void *closure, int replace)
489 {
490         int idx;
491         void *value;
492         struct cookie *cookie, **prv;
493
494         /* get key hashed index */
495         idx = cookeyidx(key);
496
497         /* lock session and search for the cookie of 'key' */
498         lock(session);
499         prv = &session->cookies[idx];
500         for (;;) {
501                 cookie = *prv;
502                 if (!cookie) {
503                         /* 'key' not found, create value using 'closure' and 'makecb' */
504                         value = makecb ? makecb(closure) : closure;
505                         /* store the the only if it has some meaning */
506                         if (replace || makecb || freecb) {
507                                 cookie = malloc(sizeof *cookie);
508                                 if (!cookie) {
509                                         errno = ENOMEM;
510                                         /* calling freecb if there is no makecb may have issue */
511                                         if (makecb && freecb)
512                                                 freecb(value);
513                                         value = NULL;
514                                 } else {
515                                         cookie->key = key;
516                                         cookie->value = value;
517                                         cookie->freecb = freecb;
518                                         cookie->next = NULL;
519                                         *prv = cookie;
520                                 }
521                         }
522                         break;
523                 } else if (cookie->key == key) {
524                         /* cookie of key found */
525                         if (!replace)
526                                 /* not replacing, get the value */
527                                 value = cookie->value;
528                         else {
529                                 /* create value using 'closure' and 'makecb' */
530                                 value = makecb ? makecb(closure) : closure;
531
532                                 /* free previous value is needed */
533                                 if (cookie->value != value && cookie->freecb)
534                                         cookie->freecb(cookie->value);
535
536                                 /* store the value and its releaser */
537                                 cookie->value = value;
538                                 cookie->freecb = freecb;
539
540                                 /* but if both are NULL drop the cookie */
541                                 if (!value && !freecb) {
542                                         *prv = cookie->next;
543                                         free(cookie);
544                                 }
545                         }
546                         break;
547                 } else {
548                         prv = &(cookie->next);
549                 }
550         }
551
552         /* unlock the session and return the value */
553         unlock(session);
554         return value;
555 }
556
557 void *afb_session_get_cookie(struct afb_session *session, const void *key)
558 {
559         return afb_session_cookie(session, key, NULL, NULL, NULL, 0);
560 }
561
562 int afb_session_set_cookie(struct afb_session *session, const void *key, void *value, void (*freecb)(void*))
563 {
564         return -(value != afb_session_cookie(session, key, NULL, freecb, value, 1));
565 }
566