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