call_obj: software license.
[sip-router] / src / modules / call_obj / cobj.c
1 /*
2  * CALL_OBJ module
3  *
4  * Copyright (C) 2017-2019 - Sonoc
5  *
6  * This file is part of Kamailio, a free SIP server.
7  *
8  * Kamailio is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation; either version 2 of the License, or
11  * (at your option) any later version
12  *
13  * Kamailio is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
21  *
22  */
23
24 /**
25  * Functionality of call_obj module.
26  */
27
28 #include <stdbool.h>
29 #include <stdint.h>
30 #include <inttypes.h>
31 #include <sys/time.h>
32
33 #include "../../core/mem/shm_mem.h"
34 #include "../../core/locking.h"
35
36 #include "cobj.h"
37
38 /**
39  * Element of the array.
40  * When assigned equals false other contents are undefined.
41  */
42 typedef struct {
43         bool assigned;
44         uint64_t timestamp;
45         str callid;
46 } co_object_t;
47
48 typedef struct {
49         int start; /* Number of first object. */
50         int end; /* Number of last object (included). */
51         /**
52          * Current position of last assigned object.
53          * 0 - no object has been assigned yet.
54          */
55         int cur;
56         int assigned; /* Number of assigned objects at this moment. */
57         gen_lock_t *lock; /* Lock to protect ring array. */
58         co_object_t *ring; /* Array of call objects. */
59 } co_data_t;
60
61 /**
62  * Struct containing all call object related data.
63  */
64 static co_data_t *co_data = NULL;
65
66 /**
67  * Initialize call object module.
68  *
69  * /return 0 on success.
70  */
71 int cobj_init(int start, int end)
72 {
73         
74         if (start == 0) {
75                 LM_ERR("Wrong start value\n");
76                 return -1;
77         }
78         if (end == 0) {
79                 LM_ERR("Wrong end value\n");
80                 return -1;
81         }
82         if (start > end) {
83                 LM_ERR("End value should be greater than start one [%d, %d]\n", start, end);
84                 return -1;
85         }
86
87         co_data = (co_data_t*)shm_malloc(sizeof(co_data_t));
88         if (!co_data) {
89                 LM_ERR("Cannot allocate shm memory for call object\n");
90                 return -1;
91         }
92
93         co_data->start = start;
94         co_data->end = end;
95         co_data->cur = 0; /* No object assigned yet. */
96         co_data->assigned = 0; /* No assigned objects at this moment. */
97         co_data->lock = NULL;
98         co_data->ring = NULL;
99         
100         size_t total_size = (1 + end - start); /* [start, end] */
101         size_t array_size = total_size * sizeof(co_object_t);
102         LM_DBG("Element size: %lu\n", sizeof(co_object_t));
103         LM_DBG("List element size: %lu\n", sizeof(cobj_elem_t));
104         
105         co_data->ring = (co_object_t*)shm_malloc(array_size);
106         if (!co_data->ring) {
107                 LM_ERR("Cannot allocate shm memory for ring in call object\n");
108                 return -1;
109         }
110         LM_DBG("Allocated %lu bytes for the ring\n", array_size);
111
112         /**
113          * Initialize lock.
114          */
115         co_data->lock = lock_alloc();
116         if (!co_data->lock) {
117                 LM_ERR("Cannot allocate lock\n");
118                 return -1;
119         }
120
121         if(lock_init(co_data->lock)==NULL)
122         {
123                 LM_ERR("cannot init the lock\n");
124                 lock_dealloc(co_data->lock);
125                 co_data->lock = NULL;
126                 return -1;
127         }
128         
129         co_data->cur = 0; /* No object assigned yet. */
130
131         co_data->start = start;
132         co_data->end = end;
133         
134         /* Every object is set as free. */
135         int i;
136         for (i=0; i<total_size; i++) {
137                 co_data->ring[i].assigned = false;
138         }
139         /* timestamp, etc is undefined. */
140         
141         LM_DBG("Call object Init: cur=%d  start=%d  end=%d\n",
142                    co_data->cur, co_data->start, co_data->end);
143         return 0;
144 }
145
146 /**
147  * Close call object module.
148  */
149 void cobj_destroy(void)
150 {
151         if (!co_data) {
152                 /* Nothing to free. */
153                 return;
154         }
155         
156         /* Free lock */
157         if (co_data->lock) {
158                 LM_DBG("Freeing lock\n");
159                 lock_destroy(co_data->lock);
160                 lock_dealloc(co_data->lock);
161                 co_data->lock = NULL;
162         }
163
164         /* Free ring array. */
165         if (co_data->ring) {
166                 LM_DBG("Freeing call object ring\n");
167                 shm_free(co_data->ring);
168                 co_data->ring = NULL;
169         }
170
171         assert(co_data);
172         shm_free(co_data);
173         co_data = NULL;
174 }
175
176 /**
177  * Get current timestamp in milliseconds.
178  *
179  * /param ts pointer to timestamp integer.
180  * /return 0 on success.
181  */
182 int get_timestamp(uint64_t *ts)
183 {
184         assert(ts);
185         
186         struct timeval current_time;
187         if (gettimeofday(&current_time, NULL) < 0) {
188                 LM_ERR("failed to get current time!\n");
189                 return -1;
190         }
191
192         *ts = (uint64_t)current_time.tv_sec*1000 +
193                 (uint64_t)current_time.tv_usec/1000;
194         
195         return 0;
196 }
197
198 /**
199  * Fill an object with data.
200  *
201  * /return 0 on success.
202  */
203 static int cobj_fill(co_object_t *obj, uint64_t timestamp, str *callid)
204 {
205         assert(obj->assigned == false);
206         
207         int res = -1;
208         
209         obj->callid.s = (char*)shm_malloc(callid->len + 1); /* +1 Zero at the end */
210         if (!obj->callid.s) {
211                 LM_ERR("Cannot allocate memory for callid\n");
212                 goto clean;
213         }
214         memcpy(obj->callid.s, callid->s, callid->len);
215         obj->callid.s[callid->len] = '\0';
216         obj->callid.len = callid->len;
217
218         /* Assign timestamp */
219         obj->timestamp = timestamp;
220         
221         /* Everything went fine. */
222         obj->assigned = true;
223         res = 0;
224 clean:  
225         return res;
226 }
227
228 /**
229  * Get a free object.
230  *
231  * /param timestamp assign this timestamp to the object we get.
232  * /param callid pointer to callid str.
233  * /return -1 if an error happens.
234  * /return number of a free object on success.
235  */
236 int cobj_get(uint64_t timestamp, str *callid)
237 {
238         assert(callid);
239         assert(callid->len > 0);
240         
241         int res = -1; /* Error by default */
242
243         lock_get(co_data->lock);
244
245         LM_DBG("IN co_data->cur: %d\n", co_data->cur);
246
247         if (co_data->cur == 0) {
248                 /* First object to assign. */
249                 co_object_t *obj = &co_data->ring[0];
250                 if (cobj_fill(obj, timestamp, callid)) {
251                         LM_ERR("Cannot create object 0\n");
252                         goto clean;
253                 }
254         
255                 co_data->cur = co_data->start;
256                 res = co_data->cur;
257                 co_data->assigned++;
258                 LM_DBG("Object found: %d\n", res);
259                 LM_DBG("Current timestamp: %" PRIu64 "\n", obj->timestamp);
260                 LM_DBG("Current Call-ID: %.*s\n", obj->callid.len, obj->callid.s);
261                 goto clean;
262         }
263         assert(co_data->cur >= co_data->start && co_data->cur <= co_data->end);
264
265         /* Find next free position in array. */
266         int pos_cur, pos, pos_max;
267         pos_cur = co_data->cur - co_data->start; /* Last used position in array. */
268         pos = pos_cur + 1; /* Position to check in array. */
269         pos_max = co_data->end - co_data->start; /* Maximum acceptable position in array. */
270         
271         while (pos != pos_cur) {
272                 if (pos > pos_max) {
273                         pos = 0;
274                         continue;
275                 }
276                 assert(pos <= pos_max && pos >= 0);
277
278                 co_object_t *obj = &co_data->ring[pos];
279                 if (obj->assigned == false) {
280                         /* We found a free object. */
281                   if (cobj_fill(obj, timestamp, callid)) {
282                                 LM_ERR("Cannot create object %d\n", pos);
283                                 goto clean;
284                         }
285                         
286                         co_data->cur = pos + co_data->start;
287                         res = co_data->cur;
288                         co_data->assigned++;
289                         LM_DBG("Object found: %d\n", res);
290                         LM_DBG("Current timestamp: %" PRIu64 "\n", obj->timestamp);
291                         LM_DBG("Current Call-ID: %.*s\n", obj->callid.len, obj->callid.s);
292                         goto clean;
293                 }
294
295                 pos++;
296         }
297         
298         /* No free object found. */
299         res = -1;
300         LM_ERR("No free objects available\n");
301         
302 clean:
303
304         LM_DBG("OUT co_data->cur: %d\n", co_data->cur);
305         lock_release(co_data->lock);
306         return res;
307 }
308
309 /**
310  * Free an Object
311  *
312  * /param num number of object to free
313  * /return 0 on success
314  */
315 int cobj_free(int num)
316 {
317         int res = -1; // It fails by default.
318
319         lock_get(co_data->lock);
320
321         if (num < co_data->start || num > co_data->end) {
322                 LM_ERR("Object out of range %d  [%d, %d]\n", num, co_data->start, co_data->end);
323                 goto clean;
324         }
325
326         int pos = num - co_data->start;
327         co_object_t *obj = &co_data->ring[pos];
328         if (obj->assigned == true) {
329                 LM_DBG("Freeing object %d - timestamp: %" PRIu64 " - Call-ID: %.*s\n",
330                            num, obj->timestamp, obj->callid.len, obj->callid.s);
331
332                 if (obj->callid.s) {
333                         shm_free(obj->callid.s);
334                         obj->callid.s = NULL;
335                 }
336
337                 obj->assigned = false;
338                 co_data->assigned--;
339         } else {
340                 LM_WARN("Freeing an already free object: %d\n", num);
341         }
342         res = 0;
343         LM_DBG("Object %d freed\n", num);
344         
345 clean:          
346         lock_release(co_data->lock);
347         return res;
348 }
349
350 /**
351  * Fill data in cobj_stats_t structure passed as pointer.
352  *
353  * /param stats pointer to cobj_stats_t structure.
354  * /return 0 on success
355  */
356 int cobj_stats_get(cobj_stats_t *stats)
357 {
358         int result = -1; /* It fails by default. */
359
360         lock_get(co_data->lock);
361         
362         if (!stats) {
363                 LM_ERR("No cobj_stats_t structure provided\n");
364                 goto clean;
365         }
366
367         stats->start = co_data->start;
368         stats->end = co_data->end;
369         stats->assigned = co_data->assigned;
370         
371         /* TODO */
372
373         /* Everything went fine. */
374         result = 0;
375         
376 clean:
377         lock_release(co_data->lock);
378         return result;
379 }
380
381 /**
382  * Free all objects at once.
383  */
384 void cobj_free_all(void)
385 {
386         lock_get(co_data->lock);        
387
388         int i;
389         int start = co_data->start;
390         int end = co_data->end;
391         int total = end - start + 1;
392
393         /* Free all objects in the array. */
394         for (i=0; i < total; i++) {
395
396                 co_object_t *obj = &co_data->ring[i];
397                 if (obj->assigned == true) {
398                         if (obj->callid.s) {
399                                 shm_free(obj->callid.s);
400                                 obj->callid.s = NULL;
401                         }
402                         obj->assigned = false;
403                 }
404
405         } // for i
406
407         co_data->cur = 0; /* No object assigned yet. */
408         co_data->assigned = 0; /* No assigned objects at this moment. */
409
410         LM_DBG("Objects in range [%d, %d] freed\n", start, end);
411
412         lock_release(co_data->lock);
413 }
414
415 /**
416  * Get all objects which timestamp is less than or equals some value.
417  *
418  * User shall free returned list when not used any more.
419  *
420  * /param ts timestamp to compare.
421  * /param elem returned list. NULL on error of if zero elements.
422  * /param limit maximum number of objects to return. 0 means unlimited.
423  *
424  * /return number of returned objects on success.
425  * /return -1 on error
426  */
427 int cobj_get_timestamp(uint64_t ts, cobj_elem_t **elem, int limit)
428 {
429         assert(elem);
430         assert(limit >= 0);
431
432         LM_DBG("Received timestamp: %" PRIu64 "\n", ts);
433         
434         int res = -1; /* Fail by default; */
435         *elem = NULL; /* Empty list by default. */
436
437         int total = co_data->end - co_data->start + 1;
438         int num_objects = 0; /* Not found any object by now. */
439
440         /* First and last element of the list. */
441         cobj_elem_t *first = NULL;
442         
443         int i;
444         for (i=0; i<total; i++) {
445                 co_object_t *obj = &co_data->ring[i];
446                 if (obj->assigned == true && obj->timestamp <= ts) {
447                         /* Object found */
448
449                         cobj_elem_t *elem_new = (cobj_elem_t*)pkg_malloc(sizeof(cobj_elem_t));
450                         if (!elem_new) {
451                                 LM_ERR("Memory error\n");
452                                 goto clean;
453                         }
454
455                         /* Fill new element with data */
456                         elem_new->number = co_data->start + i;
457                         elem_new->timestamp = obj->timestamp;
458                         elem_new->next = NULL;
459                         elem_new->callid.s = (char*)pkg_malloc(obj->callid.len + 1); /* +1 Zero at the end */
460                         if (!elem_new->callid.s) {
461                                 LM_ERR("Cannot allocate memory for callid\n");
462                                 pkg_free(elem_new);
463                                 elem_new = NULL;
464                                 goto clean;
465                         }
466                         memcpy(elem_new->callid.s, obj->callid.s, obj->callid.len);
467                         elem_new->callid.s[obj->callid.len] = '\0';
468                         elem_new->callid.len = obj->callid.len;
469
470                         /* Insert the element in the ascending ordered list. */
471                         cobj_elem_t *previous = NULL;
472                         cobj_elem_t *tmp = first;
473                         while (tmp) {
474                                 if (elem_new->timestamp <= tmp->timestamp) {
475                                         /* We found the position of the new element. */
476                                         break;
477                                 }
478                                 previous = tmp;
479                                 tmp = tmp->next;
480                         }
481
482                         if (previous) {
483                                 /* Non-void list. */
484                                 elem_new->next = previous->next;
485                                 previous->next = elem_new;
486                                 
487                         } else {
488                                 /* Insert at the beginning. */
489                                 elem_new->next = first;
490                                 first = elem_new;
491                         }
492                         num_objects++;
493
494                         /* Delete an element if we surpassed the limit. */
495                         if (limit && num_objects > limit) {
496                                 tmp = first;
497                                 first = first->next;
498                                 tmp->next = NULL;
499                                 cobj_free_list(tmp);
500                         }
501
502                 } /* if obj->assigned */
503                 
504         } /* for i=0 */
505                  
506         /* Everything went fine */
507         res = num_objects;
508         *elem = first;
509         first = NULL;
510         
511 clean:
512         if (first) {
513                 /* An error occurred */
514                 cobj_free_list(first);
515         }
516         
517         return res;
518 }
519
520 /**
521  * Free an object list.
522  *
523  * /param elem pointer to first element in the list.
524  */
525 void cobj_free_list(cobj_elem_t *elem)
526 {
527         while (elem) {
528                 cobj_elem_t *next = elem->next;
529                 if (elem->callid.s) {
530                         pkg_free(elem->callid.s);
531                 }
532                 pkg_free(elem);
533                 elem = next;
534         }
535 }