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