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