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