example of integration with websocket in C
[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 = sessions.store[idx];
201                 if (ctx != NULL && ctxStoreTooOld(ctx, now)) {
202                         ctxClientClose (ctx);
203                 }
204         }
205 }
206
207 // This function will return exiting client context or newly created client context
208 struct AFB_clientCtx *ctxClientGetSession (const char *uuid, int *created)
209 {
210         struct AFB_clientCtx *clientCtx;
211         time_t now;
212
213         /* cleaning */
214         now = NOW;
215         ctxStoreCleanUp (now);
216
217         /* search for an existing one not too old */
218         if (uuid != NULL) {
219                 if (strlen(uuid) >= sizeof clientCtx->uuid) {
220                         errno = EINVAL;
221                         goto error;
222                 }
223                 clientCtx = ctxStoreSearch(uuid);
224                 if (clientCtx != NULL) {
225                         *created = 0;
226                         goto found;
227                 }
228         }
229
230         /* returns a new one */
231         clientCtx = calloc(1, sizeof(struct AFB_clientCtx) + ((unsigned)sessions.apicount * sizeof(*clientCtx->values)));
232         if (clientCtx == NULL) {
233                 errno = ENOMEM;
234                 goto error;
235         }
236         clientCtx->values = (void*)(clientCtx + 1);
237
238         /* generate the uuid */
239         if (uuid == NULL) {
240                 new_uuid(clientCtx->uuid);
241         } else {
242                 strcpy(clientCtx->uuid, uuid);
243         }
244
245         /* init the token */
246         strcpy(clientCtx->token, sessions.initok);
247         clientCtx->expiration = now + sessions.timeout;
248         if (!ctxStoreAdd (clientCtx)) {
249                 errno = ENOMEM;
250                 goto error2;
251         }
252         *created = 1;
253
254 found:
255         clientCtx->access = now;
256         clientCtx->refcount++;
257         return clientCtx;
258
259 error2:
260         free(clientCtx);
261 error:
262         return NULL;
263 }
264
265 struct AFB_clientCtx *ctxClientAddRef(struct AFB_clientCtx *clientCtx)
266 {
267         if (clientCtx != NULL)
268                 clientCtx->refcount++;
269         return clientCtx;
270 }
271
272 void ctxClientUnref(struct AFB_clientCtx *clientCtx)
273 {
274         if (clientCtx != NULL) {
275                 assert(clientCtx->refcount != 0);
276                 --clientCtx->refcount;
277                 if (clientCtx->refcount == 0 && clientCtx->uuid[0] == 0) {
278                         ctxStoreDel (clientCtx);
279                         free(clientCtx);
280                 }
281         }
282 }
283
284 // Free Client Session Context
285 void ctxClientClose (struct AFB_clientCtx *clientCtx)
286 {
287         assert(clientCtx != NULL);
288         if (clientCtx->uuid[0] != 0) {
289                 clientCtx->uuid[0] = 0;
290                 ctxUuidFreeCB (clientCtx);
291                 while(clientCtx->listeners != NULL)
292                         ctxClientEventListenerRemove(clientCtx, clientCtx->listeners->listener);
293                 if (clientCtx->refcount == 0) {
294                         ctxStoreDel (clientCtx);
295                         free(clientCtx);
296                 }
297         }
298 }
299
300 // Sample Generic Ping Debug API
301 int ctxTokenCheck (struct AFB_clientCtx *clientCtx, const char *token)
302 {
303         assert(clientCtx != NULL);
304         assert(token != NULL);
305
306         // compare current token with previous one
307         if (!ctxIsActive (clientCtx, NOW))
308                 return 0;
309
310         if (clientCtx->token[0] && strcmp (token, clientCtx->token) != 0)
311                 return 0;
312
313         return 1;
314 }
315
316 // generate a new token and update client context
317 void ctxTokenNew (struct AFB_clientCtx *clientCtx)
318 {
319         assert(clientCtx != NULL);
320
321         // Old token was valid let's regenerate a new one
322         new_uuid(clientCtx->token);
323
324         // keep track of time for session timeout and further clean up
325         clientCtx->expiration = NOW + sessions.timeout;
326 }
327
328 static int add_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
329 {
330         struct afb_event_listener_list *iter, **prv;
331
332         prv = head;
333         for (;;) {
334                 iter = *prv;
335                 if (iter == NULL) {
336                         iter = calloc(1, sizeof *iter);
337                         if (iter == NULL) {
338                                 errno = ENOMEM;
339                                 return -1;
340                         }
341                         iter->listener = listener;
342                         iter->refcount = 1;
343                         *prv = iter;
344                         return 0;
345                 }
346                 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
347                         iter->refcount++;
348                         return 0;
349                 }
350                 prv = &iter->next;
351         }
352 }
353
354 int ctxClientEventListenerAdd(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
355 {
356         return add_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
357 }
358
359 static void remove_listener(struct afb_event_listener_list **head, struct afb_event_listener listener)
360 {
361         struct afb_event_listener_list *iter, **prv;
362
363         prv = head;
364         for (;;) {
365                 iter = *prv;
366                 if (iter == NULL)
367                         return;
368                 if (iter->listener.itf == listener.itf && iter->listener.closure == listener.closure) {
369                         if (!--iter->refcount) {
370                                 *prv = iter->next;
371                                 free(iter);
372                         }
373                         return;
374                 }
375                 prv = &iter->next;
376         }
377 }
378
379 void ctxClientEventListenerRemove(struct AFB_clientCtx *clientCtx, struct afb_event_listener listener)
380 {
381         remove_listener(clientCtx != NULL ? &clientCtx->listeners : &sessions.listeners, listener);
382 }
383
384 static int send(struct afb_event_listener_list *head, const char *event, struct json_object *object)
385 {
386         struct afb_event_listener_list *iter;
387         int result;
388
389         result = 0;
390         iter = head;
391         while (iter != NULL) {
392                 if (iter->listener.itf->expects == NULL || iter->listener.itf->expects(iter->listener.closure, event)) {
393                         iter->listener.itf->send(iter->listener.closure, event, json_object_get(object));
394                         result++;
395                 }
396                 iter = iter->next;
397         }
398
399         return result;
400 }
401
402 int ctxClientEventSend(struct AFB_clientCtx *clientCtx, const char *event, struct json_object *object)
403 {
404         long idx;
405         time_t now;
406         int result;
407
408         now = NOW;
409         if (clientCtx != NULL) {
410                 result = ctxIsActive(clientCtx, now) ? send(clientCtx->listeners, event, object) : 0;
411         } else {
412                 result = send(sessions.listeners, event, object);
413                 for (idx=0; idx < sessions.max; idx++) {
414                         clientCtx = ctxClientAddRef(sessions.store[idx]);
415                         if (clientCtx != NULL && ctxIsActive(clientCtx, now)) {
416                                 clientCtx = ctxClientAddRef(clientCtx);
417                                 result += send(clientCtx->listeners, event, object);
418                         }
419                         ctxClientUnref(clientCtx);
420                 }
421         }
422         return result;
423 }
424
425 const char *ctxClientGetUuid (struct AFB_clientCtx *clientCtx)
426 {
427         assert(clientCtx != NULL);
428         return clientCtx->uuid;
429 }
430
431 const char *ctxClientGetToken (struct AFB_clientCtx *clientCtx)
432 {
433         assert(clientCtx != NULL);
434         return clientCtx->token;
435 }
436
437 void *ctxClientValueGet(struct AFB_clientCtx *clientCtx, int index)
438 {
439         assert(clientCtx != NULL);
440         assert(index >= 0);
441         assert(index < sessions.apicount);
442         return clientCtx->values[index].value;
443 }
444
445 void ctxClientValueSet(struct AFB_clientCtx *clientCtx, int index, void *value, void (*free_value)(void*))
446 {
447         struct client_value prev;
448         assert(clientCtx != NULL);
449         assert(index >= 0);
450         assert(index < sessions.apicount);
451         prev = clientCtx->values[index];
452         clientCtx->values[index] = (struct client_value){.value = value, .free_value = free_value};
453         if (prev.value !=  NULL && prev.free_value != NULL)
454                 prev.free_value(prev.value);
455 }