allows global event listeners
[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   const char *initok;
68   struct afb_event_listener_list *listeners;
69 } sessions;
70
71 // Free context [XXXX Should be protected again memory abort XXXX]
72 static void ctxUuidFreeCB (struct AFB_clientCtx *client)
73 {
74         int idx;
75
76         // If application add a handle let's free it now
77         assert (client->values != NULL);
78
79         // Free client handle with a standard Free function, with app callback or ignore it
80         for (idx=0; idx < sessions.apicount; idx ++)
81                 ctxClientValueSet(client, idx, NULL, NULL);
82 }
83
84 // Create a new store in RAM, not that is too small it will be automatically extended
85 void ctxStoreInit (int max_session_count, int timeout, const char *initok, int context_count)
86 {
87
88         // let's create as store as hashtable does not have any
89         sessions.store = calloc (1 + (unsigned)max_session_count, sizeof(struct AFB_clientCtx));
90         sessions.max = max_session_count;
91         sessions.timeout = timeout;
92         sessions.apicount = context_count;
93         if (!initok) {
94                 ERROR("\"--token=\" parameter is mandatory");
95                 exit(1);
96         }
97         if (strlen(initok) >= sizeof(sessions.store[0]->token)) {
98                 ERROR("initial token '%s' too long (max length 36)", initok);
99                 exit(1);
100         }
101         sessions.initok = initok;
102 }
103
104 static struct AFB_clientCtx *ctxStoreSearch (const char* uuid)
105 {
106     int  idx;
107     struct AFB_clientCtx *client;
108
109     assert (uuid != NULL);
110
111     pthread_mutex_lock(&sessions.mutex);
112
113     for (idx=0; idx < sessions.max; idx++) {
114         client = sessions.store[idx];
115         if (client && (0 == strcmp (uuid, client->uuid)))
116                 goto found;
117     }
118     client = NULL;
119
120 found:
121     pthread_mutex_unlock(&sessions.mutex);
122     return client;
123 }
124
125 static int ctxStoreDel (struct AFB_clientCtx *client)
126 {
127     int idx;
128     int status;
129
130     assert (client != NULL);
131
132     pthread_mutex_lock(&sessions.mutex);
133
134     for (idx=0; idx < sessions.max; idx++) {
135         if (sessions.store[idx] == client) {
136                 sessions.store[idx] = NULL;
137                 sessions.count--;
138                 status = 1;
139                 goto deleted;
140         }
141     }
142     status = 0;
143 deleted:
144     pthread_mutex_unlock(&sessions.mutex);
145     return status;
146 }
147
148 static int ctxStoreAdd (struct AFB_clientCtx *client)
149 {
150     int idx;
151     int status;
152
153     assert (client != NULL);
154
155     pthread_mutex_lock(&sessions.mutex);
156
157     for (idx=0; idx < sessions.max; idx++) {
158         if (NULL == sessions.store[idx]) {
159                 sessions.store[idx] = client;
160                 sessions.count++;
161                 status = 1;
162                 goto added;
163         }
164     }
165     status = 0;
166 added:
167     pthread_mutex_unlock(&sessions.mutex);
168     return status;
169 }
170
171 // Check if context timeout or not
172 static int ctxStoreTooOld (struct AFB_clientCtx *ctx, time_t now)
173 {
174     assert (ctx != NULL);
175     return ctx->expiration < now;
176 }
177
178 // Check if context is active or not
179 static int ctxIsActive (struct AFB_clientCtx *ctx, time_t now)
180 {
181     assert (ctx != NULL);
182     return ctx->uuid[0] != 0 && ctx->expiration >= now;
183 }
184
185 // Loop on every entry and remove old context sessions.hash
186 static void ctxStoreCleanUp (time_t now)
187 {
188         struct AFB_clientCtx *ctx;
189         long idx;
190
191         // Loop on Sessions Table and remove anything that is older than timeout
192         for (idx=0; idx < sessions.max; idx++) {
193                 ctx = sessions.store[idx];
194                 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
195                         ctxClientClose (ctx);
196                 }
197         }
198 }
199
200 // This function will return exiting client context or newly created client context
201 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
202 {
203         uuid_t newuuid;
204         struct AFB_clientCtx *clientCtx;
205         time_t now;
206
207         /* cleaning */
208         now = NOW;
209         ctxStoreCleanUp (now);
210
211         /* search for an existing one not too old */
212         if (uuid != NULL) {
213                 if (strlen(uuid) >= sizeof clientCtx->uuid) {
214                         errno = EINVAL;
215                         goto error;
216                 }
217                 clientCtx = ctxStoreSearch(uuid);
218                 if (clientCtx != NULL) {
219                         *created = 0;
220                         goto found;
221                 }
222         }
223
224         /* returns a new one */
225         clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
226         if (clientCtx == NULL) {
227                 errno = ENOMEM;
228                 goto error;
229         }
230         clientCtx->values = (void*)(clientCtx + 1);
231
232         /* generate the uuid */
233         if (uuid == NULL) {
234                 uuid_generate(newuuid);
235                 uuid_unparse_lower(newuuid, clientCtx->uuid);
236         } else {
237                 strcpy(clientCtx->uuid, uuid);
238         }
239
240         /* init the token */
241         strcpy(clientCtx->token, sessions.initok);
242         clientCtx->expiration = now + sessions.timeout;
243         if (!ctxStoreAdd (clientCtx)) {
244                 errno = ENOMEM;
245                 goto error2;
246         }
247         *created = 1;
248
249 found:
250         clientCtx->access = now;
251         clientCtx->refcount++;
252         return clientCtx;
253
254 error2:
255         free(clientCtx);
256 error:
257         return NULL;
258 }
259
260 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
261 {
262         if (clientCtx != NULL)
263                 clientCtx->refcount++;
264         return clientCtx;
265 }
266
267 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
268 {
269         if (clientCtx != NULL) {
270                 assert(clientCtx->refcount != 0);
271                 --clientCtx->refcount;
272                 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
273                         ctxStoreDel (clientCtx);
274                 }
275         }
276 }
277
278 // Free Client Session Context
279 void ctxClientClose (struct AFB_clientCtx *clientCtx)
280 {
281         assert(clientCtx != NULL);
282         ctxUuidFreeCB (clientCtx);
283         clientCtx->uuid[0] = 0;
284 }
285
286 // Sample Generic Ping Debug API
287 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
288 {
289         assert(clientCtx != NULL);
290         assert(token != NULL);
291
292         // compare current token with previous one
293         if (!ctxIsActive (clientCtx, NOW))
294                 return 0;
295
296         if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
297                 return 0;
298
299         return 1;
300 }
301
302 // generate a new token and update client context
303 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
304 {
305         uuid_t newuuid;
306
307         assert(clientCtx != NULL);
308
309         // Old token was valid let's regenerate a new one
310         uuid_generate(newuuid);         // create a new UUID
311         uuid_unparse_lower(newuuid, clientCtx->token);
312
313         // keep track of time for session timeout and further clean up
314         clientCtx->expiration = NOW + sessions.timeout;
315 }
316
317 static int add_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
318 {
319         struct afb_event_listener_list *iter, **prv;
320
321         prv = head;
322         for (;;) {
323                 iter = *prv;
324                 if (iter == NULL) {
325                         iter = calloc(1, sizeof *iter);
326                         if (iter == NULL) {
327                                 errno = ENOMEM;
328                                 return -1;
329                         }
330                         iter->listener = listener;
331                         iter->refcount = 1;
332                         *prv = iter;
333                         return 0;
334                 }
335                 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
336                         iter->refcount++;
337                         return 0;
338                 }
339                 prv = &iter->next;
340         }
341 }
342
343 int ctxClientEventListenerAdd(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
344 {
345         return add_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
346 }
347
348 static void remove_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
349 {
350         struct afb_event_listener_list *iter, **prv;
351
352         prv = head;
353         for (;;) {
354                 iter = *prv;
355                 if (iter == NULL)
356                         return;
357                 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
358                         if (!--iter->refcount) {
359                                 *prv = iter->next;
360                                 free(iter);
361                         }
362                         return;
363                 }
364                 prv = &iter->next;
365         }
366 }
367
368 void ctxClientEventListenerRemove(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
369 {
370         remove_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
371 }
372
373 static int send(struct afb_event_listener_list *head, const char *event, struct json_object *object)
374 {
375         struct afb_event_listener_list *iter;
376         int result;
377
378         result = 0;
379         iter = head;
380         while (iter != NULL) {
381                 iter->listener.itf->send(iter->listener.closure, event, json_object_get(object));
382                 result++;
383                 iter = iter->next;
384         }
385
386         return result;
387 }
388
389 int ctxClientEventSend(struct AFB_clientCtx *clientCtx, const char *event, struct json_object *object)
390 {
391         long idx;
392         time_t now;
393         int result;
394
395         now = NOW;
396         if (clientCtx != NULL) {
397                 result = ctxIsActive(clientCtx, now) ? send(clientCtx->listeners, event, object) : 0;
398         } else {
399                 result = send(sessions.listeners, event, object);
400                 for (idx=0; idx < sessions.max; idx++) {
401                         clientCtx = ctxClientAddRef(sessions.store[idx]);
402                         if (clientCtx != NULL && ctxIsActive(clientCtx, now)) {
403                                 clientCtx = ctxClientAddRef(clientCtx);
404                                 result += send(clientCtx->listeners, event, object);
405                         }
406                         ctxClientUnref(clientCtx);
407                 }
408         }
409         return result;
410 }
411
412 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
413 {
414         assert(clientCtx != NULL);
415         return clientCtx->uuid;
416 }
417
418 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
419 {
420         assert(clientCtx != NULL);
421         return clientCtx->token;
422 }
423
424 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
425 {
426         assert(clientCtx != NULL);
427         assert(index >= 0);
428         assert(index < sessions.apicount);
429         return clientCtx->values[index].value;
430 }
431
432 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
433 {
434         struct client_value prev;
435         assert(clientCtx != NULL);
436         assert(index >= 0);
437         assert(index < sessions.apicount);
438         prev = clientCtx->values[index];
439         clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
440         if (prev.value !=  NULL && prev.free_value != NULL)
441                 prev.free_value(prev.value);
442 }