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