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