22e50cfc5075a8400c9657fdf4718d951d62f9c1
[src/app-framework-binder.git] / src / session.c
1 /*
2  * Copyright (C) 2015 "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         time_t expiration;    // expiration time of the token
52         time_t access;
53         char uuid[37];        // long term authentication of remote client
54         char token[37];       // short term authentication of remote client
55         struct client_value *values;
56         struct afb_event_listener_list *listeners;
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_clientCtx **store;          // sessions store
63   int count;                      // current number of sessions
64   int max;
65   int timeout;
66   int apicount;
67   char initok[37];
68   struct afb_event_listener_list *listeners;
69 } sessions;
70
71 /* generate a uuid */
72 static void new_uuid(char uuid[37])
73 {
74         uuid_t newuuid;
75         uuid_generate(newuuid);
76         uuid_unparse_lower(newuuid, uuid);
77 }
78
79 // Free context [XXXX Should be protected again memory abort XXXX]
80 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
81 {
82         int idx;
83
84         // If application add a handle let's free it now
85         assert (client->values != NULL);
86
87         // Free client handle with a standard Free function, with app callback or ignore it
88         for (idx=0; idx < sessions.apicount; idx ++)
89                 ctxClientValueSet(client, idx, NULL, NULL);
90 }
91
92 // Create a new store in RAM, not that is too small it will be automatically extended
93 void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count)
94 {
95         // let's create as store as hashtable does not have any
96         sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx));
97         sessions.max = max_session_count;
98         sessions.timeout = timeout;
99         sessions.apicount = context_count;
100         if (initok == NULL)
101                 /* without token, a secret is made to forbid creation of sessions */
102                 new_uuid(sessions.initok);
103         else if (strlen(initok) < sizeof(sessions.store[0]->token))
104                 strcpy(sessions.initok, initok);
105         else {
106                 ERROR("initial token '%s' too long (max length 36)", initok);
107                 exit(1);
108         }
109 }
110
111 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
112 {
113     int  idx;
114     struct AFB_clientCtx *client;
115
116     assert (uuid != NULL);
117
118     pthread_mutex_lock(&sessions.mutex);
119
120     for (idx=0; idx < sessions.max; idx++) {
121         client = sessions.store[idx];
122         if (client && (0 == strcmp (uuid, client->uuid)))
123                 goto found;
124     }
125     client = NULL;
126
127 found:
128     pthread_mutex_unlock(&sessions.mutex);
129     return client;
130 }
131
132 static int ctxStoreDel (struct AFB_clientCtx *client)
133 {
134     int idx;
135     int status;
136
137     assert (client != NULL);
138
139     pthread_mutex_lock(&sessions.mutex);
140
141     for (idx=0; idx < sessions.max; idx++) {
142         if (sessions.store[idx] == client) {
143                 sessions.store[idx] = NULL;
144                 sessions.count--;
145                 status = 1;
146                 goto deleted;
147         }
148     }
149     status = 0;
150 deleted:
151     pthread_mutex_unlock(&sessions.mutex);
152     return status;
153 }
154
155 static int ctxStoreAdd (struct AFB_clientCtx *client)
156 {
157     int idx;
158     int status;
159
160     assert (client != NULL);
161
162     pthread_mutex_lock(&sessions.mutex);
163
164     for (idx=0; idx < sessions.max; idx++) {
165         if (NULL == sessions.store[idx]) {
166                 sessions.store[idx] = client;
167                 sessions.count++;
168                 status = 1;
169                 goto added;
170         }
171     }
172     status = 0;
173 added:
174     pthread_mutex_unlock(&sessions.mutex);
175     return status;
176 }
177
178 // Check if context timeout or not
179 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
180 {
181     assert (ctx != NULL);
182     return ctx->expiration < now;
183 }
184
185 // Check if context is active or not
186 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
187 {
188     assert (ctx != NULL);
189     return ctx->uuid[0] != 0 && ctx->expiration >= now;
190 }
191
192 // Loop on every entry and remove old context sessions.hash
193 static void ctxStoreCleanUp (time_t now)
194 {
195         struct AFB_clientCtx *ctx;
196         long idx;
197
198         // Loop on Sessions Table and remove anything that is older than timeout
199         for (idx=0; idx < sessions.max; idx++) {
200                 ctx = ctxClientAddRef(sessions.store[idx]);
201                 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
202                         ctxClientClose (ctx);
203                 }
204                 ctxClientUnref(ctx);
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         }
295 }
296
297 // Sample Generic Ping Debug API
298 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
299 {
300         assert(clientCtx != NULL);
301         assert(token != NULL);
302
303         // compare current token with previous one
304         if (!ctxIsActive (clientCtx, NOW))
305                 return 0;
306
307         if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
308                 return 0;
309
310         return 1;
311 }
312
313 // generate a new token and update client context
314 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
315 {
316         assert(clientCtx != NULL);
317
318         // Old token was valid let's regenerate a new one
319         new_uuid(clientCtx->token);
320
321         // keep track of time for session timeout and further clean up
322         clientCtx->expiration = NOW + sessions.timeout;
323 }
324
325 static int add_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
326 {
327         struct afb_event_listener_list *iter, **prv;
328
329         prv = head;
330         for (;;) {
331                 iter = *prv;
332                 if (iter == NULL) {
333                         iter = calloc(1, sizeof *iter);
334                         if (iter == NULL) {
335                                 errno = ENOMEM;
336                                 return -1;
337                         }
338                         iter->listener = listener;
339                         iter->refcount = 1;
340                         *prv = iter;
341                         return 0;
342                 }
343                 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
344                         iter->refcount++;
345                         return 0;
346                 }
347                 prv = &iter->next;
348         }
349 }
350
351 int ctxClientEventListenerAdd(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
352 {
353         return add_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
354 }
355
356 static void remove_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
357 {
358         struct afb_event_listener_list *iter, **prv;
359
360         prv = head;
361         for (;;) {
362                 iter = *prv;
363                 if (iter == NULL)
364                         return;
365                 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
366                         if (!--iter->refcount) {
367                                 *prv = iter->next;
368                                 free(iter);
369                         }
370                         return;
371                 }
372                 prv = &iter->next;
373         }
374 }
375
376 void ctxClientEventListenerRemove(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
377 {
378         remove_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
379 }
380
381 static int send(struct afb_event_listener_list *head, const char *event, struct json_object *object)
382 {
383         struct afb_event_listener_list *iter;
384         int result;
385
386         result = 0;
387         iter = head;
388         while (iter != NULL) {
389                 if (iter->listener.itf->expects == NULL || iter->listener.itf->expects(iter->listener.closure, event)) {
390                         iter->listener.itf->send(iter->listener.closure, event, json_object_get(object));
391                         result++;
392                 }
393                 iter = iter->next;
394         }
395
396         return result;
397 }
398
399 int ctxClientEventSend(struct AFB_clientCtx *clientCtx, const char *event, struct json_object *object)
400 {
401         long idx;
402         time_t now;
403         int result;
404
405         now = NOW;
406         if (clientCtx != NULL) {
407                 result = ctxIsActive(clientCtx, now) ? send(clientCtx->listeners, event, object) : 0;
408         } else {
409                 result = send(sessions.listeners, event, object);
410                 for (idx=0; idx < sessions.max; idx++) {
411                         clientCtx = ctxClientAddRef(sessions.store[idx]);
412                         if (clientCtx != NULL && ctxIsActive(clientCtx, now)) {
413                                 clientCtx = ctxClientAddRef(clientCtx);
414                                 result += send(clientCtx->listeners, event, object);
415                         }
416                         ctxClientUnref(clientCtx);
417                 }
418         }
419         return result;
420 }
421
422 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
423 {
424         assert(clientCtx != NULL);
425         return clientCtx->uuid;
426 }
427
428 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
429 {
430         assert(clientCtx != NULL);
431         return clientCtx->token;
432 }
433
434 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
435 {
436         assert(clientCtx != NULL);
437         assert(index >= 0);
438         assert(index < sessions.apicount);
439         return clientCtx->values[index].value;
440 }
441
442 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
443 {
444         struct client_value prev;
445         assert(clientCtx != NULL);
446         assert(index >= 0);
447         assert(index < sessions.apicount);
448         prev = clientCtx->values[index];
449         clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
450         if (prev.value !=  NULL && prev.free_value != NULL)
451                 prev.free_value(prev.value);
452 }