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