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