diff --git a/src/modules/call_obj/Makefile b/src/modules/call_obj/Makefile new file mode 100644 index 00000000000..fabd40c4d18 --- /dev/null +++ b/src/modules/call_obj/Makefile @@ -0,0 +1,10 @@ +# +# WARNING: do not run this directly, it should be run by the master Makefile + +include ../../Makefile.defs +auto_gen= +NAME=call_obj.so + +DEFS+=-DKAMAILIO_MOD_INTERFACE + +include ../../Makefile.modules diff --git a/src/modules/call_obj/README b/src/modules/call_obj/README new file mode 100644 index 00000000000..4a54fe00fac --- /dev/null +++ b/src/modules/call_obj/README @@ -0,0 +1,247 @@ +CALL_OBJ Module + +Vicente Hernando + + + +Edited by + +Vicente Hernando + + + +Javier Gallart + + + + Copyright © 2016 www.systemonenoc.com + __________________________________________________________________ + + Table of Contents + + 1. Admin Guide + + 1. Overview + 2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + + 3. Parameters + + 3.1. start (integer) + 3.2. end (integer) + + 4. Functions + + 4.1. call_obj_get(reply_number) + 4.2. call_obj_free(object_number) + + 5. RPC Commands + + 5.1. call_obj.free + 5.2. call_obj.stats + 5.3. call_obj.free_all + 5.4. call_obj.list + + List of Examples + + 1.1. Set start parameter + 1.2. Set end parameter + 1.3. call_obj_get usage + 1.4. call_obj_free usage + 1.5. call_obj.free usage + 1.6. call_obj.stats usage + 1.7. call_obj.free_all usage + 1.8. call_obj.list usage + +Chapter 1. Admin Guide + + Table of Contents + + 1. Overview + 2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + + 3. Parameters + + 3.1. start (integer) + 3.2. end (integer) + + 4. Functions + + 4.1. call_obj_get(reply_number) + 4.2. call_obj_free(object_number) + + 5. RPC Commands + + 5.1. call_obj.free + 5.2. call_obj.stats + 5.3. call_obj.free_all + 5.4. call_obj.list + +1. Overview + + This module provides a way to identify calls using a increasing + sequence of integers. + + It starts assigning an integer to a call. Next call gets next free + integer in a ring. When a call finishes its assigned number shall be + freed. + +2. Dependencies + + 2.1. Kamailio Modules + 2.2. External Libraries or Applications + +2.1. Kamailio Modules + + The following modules must be loaded before this module: + * none. + +2.2. External Libraries or Applications + + The following libraries or applications must be installed before + running Kamailio with this module loaded: + * None. + +3. Parameters + + 3.1. start (integer) + 3.2. end (integer) + +3.1. start (integer) + + First number to assign when no number has been assigned yet. + + This parameter has no default value. Not setting it raises an error. + + Its value shall be greater than zero. + + Example 1.1. Set start parameter +... +modparam("call_obj", "start", 10) +... + +3.2. end (integer) + + Last number to assign when all other numbers have been already + assigned. + + No default value. Not setting this parameter raises an error. + + Its value shall be greater than zero. + + Example 1.2. Set end parameter +... +modparam("call_obj", "end", 93) +... + +4. Functions + + 4.1. call_obj_get(reply_number) + 4.2. call_obj_free(object_number) + +4.1. call_obj_get(reply_number) + + Get next free number. reply_number parameter is a variable where + function will store result in string format. + + Example 1.3. call_obj_get usage +... +if (call_obj_get("$dlg_var(x)")) { + xlog("Object: $dlg_var(x)\n"); +} +... + +4.2. call_obj_free(object_number) + + Mark an object_number as free, so it can be assigned again. This number + will not be readily assigned until a loop in the ring has completed. + object_number shall be provided in string format. + + Example 1.4. call_obj_free usage +... +$dlg_var(y) = "27"; +if (call_obj_free("$dlg_var(y)")) { + xlog("object $dlg_var(y) freed OK\n"); +} +... + +5. RPC Commands + + 5.1. call_obj.free + 5.2. call_obj.stats + 5.3. call_obj.free_all + 5.4. call_obj.list + +5.1. call_obj.free + + Free an object. + + Parameter is a number represented in string format. + + Name: call_obj.free + + Parameters: object_number + + Example 1.5. call_obj.free usage +... +kamcmd call_obj.free s:12 +... + +5.2. call_obj.stats + + Return some statistics about call_obj module. + + It currently returns: + * Start: Number of first object + * End: Number of last object in range + * Total: Total assignable objects + * Assigned: Number of currently assigned objects + + Name: call_obj.stats + + Parameters: none + + Example 1.6. call_obj.stats usage +... +kamcmd call_obj.stats +... + +5.3. call_obj.free_all + + Make all objects free at once. + + Name: call_obj.free_all + + Parameters: none + + Example 1.7. call_obj.free_all usage +... +kamcmd call_obj.free_all +... + +5.4. call_obj.list + + List all active calls which duration is longer than a value in seconds. + + Name: call_obj.list + + Parameters: duration limit + + limit parameter is optional + + Example 1.8. call_obj.list usage +... +Show every call which duration is less than or equal to 24 seconds: +kamcmd call_obj.list 24 + +Same but limit output to at most 5 calls: +kamcmd call_obj.list 24 5 + +Same but show again all calls (0 means no limit): +kamcmd call_obj.list 24 0 +... diff --git a/src/modules/call_obj/call_obj_mod.c b/src/modules/call_obj/call_obj_mod.c new file mode 100644 index 00000000000..31bc1dade64 --- /dev/null +++ b/src/modules/call_obj/call_obj_mod.c @@ -0,0 +1,366 @@ + +#include + +#include "cobj.h" + +#include "../../core/sr_module.h" +#include "../../core/mod_fix.h" +#include "../../core/lvalue.h" +#include "../../core/rpc.h" +#include "../../core/rpc_lookup.h" +#include "../../core/trim.h" + +MODULE_VERSION + +static int w_call_obj_get(struct sip_msg *msg, char *result); + +static int w_call_obj_free(struct sip_msg* msg, char* num_obj); + +static int mod_init(void); +static void mod_destroy(void); + +/** + * Module parameters + */ +/* Actually, negative or zero values are not allowed. */ +int call_obj_start = 0; +int call_obj_end = 0; + +/* module commands */ +static cmd_export_t cmds[] = { + {"call_obj_get", (cmd_function)w_call_obj_get, 1, fixup_pvar_null, fixup_free_pvar_null, ANY_ROUTE}, + {"call_obj_free", (cmd_function)w_call_obj_free, 1, fixup_var_str_1, 0, ANY_ROUTE}, + { 0, 0, 0, 0, 0, 0} +}; + +static param_export_t params[]={ + {"start", PARAM_INT, &call_obj_start}, + {"end", PARAM_INT, &call_obj_end}, + {0, 0, 0} +}; + +/* RPC commands. */ + +static void rpc_call_obj_free(rpc_t *rpc, void *ctx) +{ + str obj_str; + int obj_num; + + if (rpc->scan(ctx, "S", &obj_str) < 1) { + rpc->fault(ctx, 400, "required object number argument"); + return; + } + + if (str2int(&obj_str, (unsigned int*)&obj_num)) { + LM_ERR("Cannot convert %.*s to number\n", obj_str.len, obj_str.s); + rpc->fault(ctx, 400, "cannot convert string to number"); + return; + } + LM_DBG("Param value: %d\n", obj_num); + + if (cobj_free(obj_num)) { + LM_ERR("Freeing object: %d\n", obj_num); + rpc->fault(ctx, 500, "error freeing object"); + return; + } + + return; +} + +static void rpc_call_obj_stats(rpc_t *rpc, void *ctx) +{ + cobj_stats_t stats; + + if (cobj_stats_get(&stats)) { + LM_ERR("Cannot get statistics for module\n"); + rpc->fault(ctx, 500, "cannot get statistics for module"); + return; + } + + if (rpc->rpl_printf(ctx, "Start: %d End: %d", stats.start, stats.end) < 0) { + return; + } + + int total = stats.end - stats.start + 1; + double percentage = 100.0 * stats.assigned / total; + if (rpc->rpl_printf(ctx, "Total: %d Assigned: %d (%.*f%%)", + total, stats.assigned, 2, percentage)) { + return; + } + + return; +} + +static void rpc_call_obj_free_all(rpc_t *rpc, void *ctx) +{ + cobj_free_all(); + + return; +} + +static void rpc_call_obj_list(rpc_t *rpc, void *ctx) +{ + int duration = 0; + int limit = 0; /* Maximum number of objects to return. 0 means unlimited. */ + cobj_elem_t *list = NULL; + + int rc = rpc->scan(ctx, "d*d", &duration, &limit); + if (rc != -1 && rc != 2) { + rpc->fault(ctx, 400, "requires arguments for duration number (and optionally limit)"); + goto clean; + } + + if (duration < 0) { + rpc->fault(ctx, 400, "duration argument shouldn\'t be negative"); + goto clean; + } + + if (limit < 0) { + rpc->fault(ctx, 400, "limit argument shouldn\'t be negative"); + goto clean; + } + + uint64_t current_ts; + uint64_t dur_ms = duration*1000; /* duration in milliseconds */ + if (get_timestamp(¤t_ts)) { + LM_ERR("error getting timestamp"); + rpc->fault(ctx, 500, "error getting timestamp"); + goto clean; + } + + if (current_ts < dur_ms) { + rpc->fault(ctx, 400, "duration is too long"); + goto clean; + } + + uint64_t timestamp = current_ts - dur_ms; + int num = cobj_get_timestamp(timestamp, &list, limit); + if (num < 0) { + rpc->fault(ctx, 500, "error getting call list"); + goto clean; + } + + rpc->rpl_printf(ctx, "Number of calls: %d", num); + if (limit && limit < num) { + rpc->rpl_printf(ctx, "Showing only: %d", limit); + } + cobj_elem_t *elem = list; + while (elem) { + rpc->rpl_printf(ctx, "%d ts: %" PRIu64 " Call-ID: %.*s", elem->number, + elem->timestamp, elem->callid.len, elem->callid.s); + elem = elem->next; + } + +clean: + + /* Free list */ + if (list) { + cobj_free_list(list); + } + + return; +} + +static const char* rpc_call_obj_free_all_doc[2] = { + "Free all objects at once", + 0 +}; + +static const char* rpc_call_obj_stats_doc[2] = { + "Show statistics about module objects", + 0 +}; + +static const char* rpc_call_obj_free_doc[2] = { + "Free an object so it can be assigned again", + 0 +}; + +static const char* rpc_call_obj_list_doc[2] = { + "Get a list of objects with a longer duration (in seconds) than a number", + 0 +}; + +static rpc_export_t rpc_cmds[] = { + {"call_obj.free", rpc_call_obj_free, rpc_call_obj_free_doc, 0}, + {"call_obj.stats", rpc_call_obj_stats, rpc_call_obj_stats_doc, 0}, + {"call_obj.free_all", rpc_call_obj_free_all, rpc_call_obj_free_all_doc, 0}, + {"call_obj.list", rpc_call_obj_list, rpc_call_obj_list_doc, 0}, + {0, 0, 0, 0} +}; + +struct module_exports exports = { + "call_obj", + DEFAULT_DLFLAGS, /* dlopen flags */ + cmds, + params, + 0, + 0, /* exported MI functions */ + 0, //mod_pvs, /* exported pseudo-variables */ + 0, /* extra processes */ + mod_init, /* module initialization function */ + 0, /* response function */ + mod_destroy, /* destroy function */ + 0 /* per child init function */ +}; + +static int mod_init(void) +{ + LM_DBG("Start parameter: %d\n", call_obj_start); + LM_DBG("End parameter: %d\n", call_obj_end); + + if (rpc_register_array(rpc_cmds) != 0) { + LM_ERR("failed to register RPC commands\n"); + return -1; + } + + if (cobj_init(call_obj_start, call_obj_end)) { + LM_ERR("Could not start module\n"); + return -1; + } + + return 0; +} + +static void mod_destroy(void) +{ + LM_DBG("cleaning up\n"); + cobj_destroy(); +} + +/** + * Looks for the Call-ID header + * On error content pointed by s is undefined. + * + * @param msg - the sip message + * @param s pointer to str where we will store callid. + * + * @returns 0 on success + */ +static int get_call_id(struct sip_msg *msg, str *s) +{ + if (!msg) { + LM_ERR("No message available\n"); + return -1; + } + + if ( (!msg->callid && parse_headers(msg, HDR_CALLID_F, 0)!=0) || msg->callid==0 ) { + LM_ERR("failed to parse Call-ID\n"); + return -1; + } + + if( msg->callid->body.s==NULL || msg->callid->body.len==0) { + LM_ERR("cannot parse Call-ID header\n"); + return -1; + } + + *s = msg->callid->body; + trim(s); + + return 0; +} + +/** + * + */ +static int w_call_obj_get(struct sip_msg *msg, char *result) +{ + int ret_code = -1; /* It fails by default. */ + + if (!msg) { + LM_ERR("No SIP message found\n"); + goto clean; + } + + pv_spec_t *res; + + if(result==NULL) + { + LM_ERR("No result variable\n"); + goto clean; + } + res = (pv_spec_t *)result; + + str call_id; + if (get_call_id(msg, &call_id)) { + LM_ERR("Cannot get callid header\n"); + goto clean; + } + LM_DBG("CallId: %.*s\n", call_id.len, call_id.s); + + uint64_t current_ts; + if (get_timestamp(¤t_ts)) { + LM_ERR("error getting timestamp"); + goto clean; + } + + int obj = cobj_get(current_ts, &call_id); + if (obj == -1) { + LM_ERR("Getting object\n"); + goto clean; + } + /* obj >= 0 */ + + pv_value_t val; + + char *obj_str = NULL; + int len = 0; + obj_str = int2str((unsigned long)obj, &len); + if (!obj_str) { + LM_ERR("Cannot convert number %d to string\n", obj); + goto clean; + } + + memset(&val, 0, sizeof(pv_value_t)); + val.flags = PV_VAL_STR; + val.rs.s = obj_str; + val.rs.len = len; + LM_DBG("Obj string: %s\n", obj_str); + + if(res->setf(msg, &res->pvp, (int)EQ_T, &val)<0) + { + LM_ERR("setting result PV failed\n"); + goto clean; + } + + ret_code = 1; + +clean: + return ret_code; +} + +/** + * + */ +static int w_call_obj_free(struct sip_msg* msg, char* num_obj) +{ + int c_obj_num = 0; + + str num_obj_str; + + if (get_str_fparam(&num_obj_str, msg, (fparam_t*)num_obj) < 0) + { + LM_ERR("failed to get object value\n"); + return -1; + } + + if (num_obj_str.len == 0) + { + LM_ERR("invalid object parameter - empty value\n"); + return -1; + } + LM_DBG("Param string value: %.*s\n", num_obj_str.len, num_obj_str.s); + + if (str2int(&num_obj_str, (unsigned int*)&c_obj_num)) { + LM_ERR("Cannot convert %.*s to number\n", num_obj_str.len, num_obj_str.s); + return -1; + } + LM_DBG("Param value: %d\n", c_obj_num); + + if (cobj_free(c_obj_num)) { + LM_ERR("Freeing object: %d\n", c_obj_num); + return -1; + } + + return 1; +} diff --git a/src/modules/call_obj/cobj.c b/src/modules/call_obj/cobj.c new file mode 100644 index 00000000000..3a6ba31e515 --- /dev/null +++ b/src/modules/call_obj/cobj.c @@ -0,0 +1,510 @@ +/** + * Functionality of call_obj module. + */ + +#include +#include +#include +#include + +#include "../../core/mem/shm_mem.h" +#include "../../core/locking.h" + +#include "cobj.h" + +/** + * Element of the array. + * When assigned equals false other contents are undefined. + */ +typedef struct { + bool assigned; + uint64_t timestamp; + str callid; +} co_object_t; + +typedef struct { + int start; /* Number of first object. */ + int end; /* Number of last object (included). */ + /** + * Current position of last assigned object. + * 0 - no object has been assigned yet. + */ + int cur; + int assigned; /* Number of assigned objects at this moment. */ + gen_lock_t *lock; /* Lock to protect ring array. */ + co_object_t *ring; /* Array of call objects. */ +} co_data_t; + +/** + * Struct containing all call object related data. + */ +static co_data_t *co_data = NULL; + +/** + * Initialize call object module. + * + * /return 0 on success. + */ +int cobj_init(int start, int end) +{ + + if (start == 0) { + LM_ERR("Wrong start value\n"); + return -1; + } + if (end == 0) { + LM_ERR("Wrong end value\n"); + return -1; + } + if (start > end) { + LM_ERR("End value should be greater than start one [%d, %d]\n", start, end); + return -1; + } + + co_data = (co_data_t*)shm_malloc(sizeof(co_data_t)); + if (!co_data) { + LM_ERR("Cannot allocate shm memory for call object\n"); + return -1; + } + + co_data->start = start; + co_data->end = end; + co_data->cur = 0; /* No object assigned yet. */ + co_data->assigned = 0; /* No assigned objects at this moment. */ + co_data->lock = NULL; + co_data->ring = NULL; + + size_t total_size = (1 + end - start); /* [start, end] */ + size_t array_size = total_size * sizeof(co_object_t); + LM_DBG("Element size: %lu\n", sizeof(co_object_t)); + LM_DBG("List element size: %lu\n", sizeof(cobj_elem_t)); + + co_data->ring = (co_object_t*)shm_malloc(array_size); + if (!co_data->ring) { + LM_ERR("Cannot allocate shm memory for ring in call object\n"); + return -1; + } + LM_DBG("Allocated %lu bytes for the ring\n", array_size); + + /** + * Initialize lock. + */ + co_data->lock = lock_alloc(); + if (!co_data->lock) { + LM_ERR("Cannot allocate lock\n"); + return -1; + } + + if(lock_init(co_data->lock)==NULL) + { + LM_ERR("cannot init the lock\n"); + lock_dealloc(co_data->lock); + co_data->lock = NULL; + return -1; + } + + co_data->cur = 0; /* No object assigned yet. */ + + co_data->start = start; + co_data->end = end; + + /* Every object is set as free. */ + int i; + for (i=0; iring[i].assigned = false; + } + /* timestamp, etc is undefined. */ + + LM_DBG("Call object Init: cur=%d start=%d end=%d\n", + co_data->cur, co_data->start, co_data->end); + return 0; +} + +/** + * Close call object module. + */ +void cobj_destroy(void) +{ + if (!co_data) { + /* Nothing to free. */ + return; + } + + /* Free lock */ + if (co_data->lock) { + LM_DBG("Freeing lock\n"); + lock_destroy(co_data->lock); + lock_dealloc(co_data->lock); + co_data->lock = NULL; + } + + /* Free ring array. */ + if (co_data->ring) { + LM_DBG("Freeing call object ring\n"); + shm_free(co_data->ring); + co_data->ring = NULL; + } + + assert(co_data); + shm_free(co_data); + co_data = NULL; +} + +/** + * Get current timestamp in milliseconds. + * + * /param ts pointer to timestamp integer. + * /return 0 on success. + */ +int get_timestamp(uint64_t *ts) +{ + assert(ts); + + struct timeval current_time; + if (gettimeofday(¤t_time, NULL) < 0) { + LM_ERR("failed to get current time!\n"); + return -1; + } + + *ts = (uint64_t)current_time.tv_sec*1000 + + (uint64_t)current_time.tv_usec/1000; + + return 0; +} + +/** + * Fill an object with data. + * + * /return 0 on success. + */ +static int cobj_fill(co_object_t *obj, uint64_t timestamp, str *callid) +{ + assert(obj->assigned == false); + + int res = -1; + + obj->callid.s = (char*)shm_malloc(callid->len + 1); /* +1 Zero at the end */ + if (!obj->callid.s) { + LM_ERR("Cannot allocate memory for callid\n"); + goto clean; + } + memcpy(obj->callid.s, callid->s, callid->len); + obj->callid.s[callid->len] = '\0'; + obj->callid.len = callid->len; + + /* Assign timestamp */ + obj->timestamp = timestamp; + + /* Everything went fine. */ + obj->assigned = true; + res = 0; +clean: + return res; +} + +/** + * Get a free object. + * + * /param timestamp assign this timestamp to the object we get. + * /param callid pointer to callid str. + * /return -1 if an error happens. + * /return number of a free object on success. + */ +int cobj_get(uint64_t timestamp, str *callid) +{ + assert(callid); + assert(callid->len > 0); + + int res = -1; /* Error by default */ + + lock_get(co_data->lock); + + LM_DBG("IN co_data->cur: %d\n", co_data->cur); + + if (co_data->cur == 0) { + /* First object to assign. */ + co_object_t *obj = &co_data->ring[0]; + if (cobj_fill(obj, timestamp, callid)) { + LM_ERR("Cannot create object 0\n"); + goto clean; + } + + co_data->cur = co_data->start; + res = co_data->cur; + co_data->assigned++; + LM_DBG("Object found: %d\n", res); + LM_DBG("Current timestamp: %" PRIu64 "\n", obj->timestamp); + LM_DBG("Current Call-ID: %.*s\n", obj->callid.len, obj->callid.s); + goto clean; + } + assert(co_data->cur >= co_data->start && co_data->cur <= co_data->end); + + /* Find next free position in array. */ + int pos_cur, pos, pos_max; + pos_cur = co_data->cur - co_data->start; /* Last used position in array. */ + pos = pos_cur + 1; /* Position to check in array. */ + pos_max = co_data->end - co_data->start; /* Maximum acceptable position in array. */ + + while (pos != pos_cur) { + if (pos > pos_max) { + pos = 0; + continue; + } + assert(pos <= pos_max && pos >= 0); + + co_object_t *obj = &co_data->ring[pos]; + if (obj->assigned == false) { + /* We found a free object. */ + if (cobj_fill(obj, timestamp, callid)) { + LM_ERR("Cannot create object %d\n", pos); + goto clean; + } + + co_data->cur = pos + co_data->start; + res = co_data->cur; + co_data->assigned++; + LM_DBG("Object found: %d\n", res); + LM_DBG("Current timestamp: %" PRIu64 "\n", obj->timestamp); + LM_DBG("Current Call-ID: %.*s\n", obj->callid.len, obj->callid.s); + goto clean; + } + + pos++; + } + + /* No free object found. */ + res = -1; + LM_ERR("No free objects available\n"); + +clean: + + LM_DBG("OUT co_data->cur: %d\n", co_data->cur); + lock_release(co_data->lock); + return res; +} + +/** + * Free an Object + * + * /param num number of object to free + * /return 0 on success + */ +int cobj_free(int num) +{ + int res = -1; // It fails by default. + + lock_get(co_data->lock); + + if (num < co_data->start || num > co_data->end) { + LM_ERR("Object out of range %d [%d, %d]\n", num, co_data->start, co_data->end); + goto clean; + } + + int pos = num - co_data->start; + co_object_t *obj = &co_data->ring[pos]; + if (obj->assigned == true) { + LM_DBG("Freeing object %d - timestamp: %" PRIu64 " - Call-ID: %.*s\n", + num, obj->timestamp, obj->callid.len, obj->callid.s); + + if (obj->callid.s) { + shm_free(obj->callid.s); + obj->callid.s = NULL; + } + + obj->assigned = false; + co_data->assigned--; + } else { + LM_WARN("Freeing an already free object: %d\n", num); + } + res = 0; + LM_DBG("Object %d freed\n", num); + +clean: + lock_release(co_data->lock); + return res; +} + +/** + * Fill data in cobj_stats_t structure passed as pointer. + * + * /param stats pointer to cobj_stats_t structure. + * /return 0 on success + */ +int cobj_stats_get(cobj_stats_t *stats) +{ + int result = -1; /* It fails by default. */ + + lock_get(co_data->lock); + + if (!stats) { + LM_ERR("No cobj_stats_t structure provided\n"); + goto clean; + } + + stats->start = co_data->start; + stats->end = co_data->end; + stats->assigned = co_data->assigned; + + /* TODO */ + + /* Everything went fine. */ + result = 0; + +clean: + lock_release(co_data->lock); + return result; +} + +/** + * Free all objects at once. + */ +void cobj_free_all(void) +{ + lock_get(co_data->lock); + + int i; + int start = co_data->start; + int end = co_data->end; + int total = end - start + 1; + + /* Free all objects in the array. */ + for (i=0; i < total; i++) { + + co_object_t *obj = &co_data->ring[i]; + if (obj->assigned == true) { + if (obj->callid.s) { + shm_free(obj->callid.s); + obj->callid.s = NULL; + } + obj->assigned = false; + } + + } // for i + + co_data->cur = 0; /* No object assigned yet. */ + co_data->assigned = 0; /* No assigned objects at this moment. */ + + LM_DBG("Objects in range [%d, %d] freed\n", start, end); + + lock_release(co_data->lock); +} + +/** + * Get all objects which timestamp is less than or equals some value. + * + * User shall free returned list when not used any more. + * + * /param ts timestamp to compare. + * /param elem returned list. NULL on error of if zero elements. + * /param limit maximum number of objects to return. 0 means unlimited. + * + * /return number of returned objects on success. + * /return -1 on error + */ +int cobj_get_timestamp(uint64_t ts, cobj_elem_t **elem, int limit) +{ + assert(elem); + assert(limit >= 0); + + LM_DBG("Received timestamp: %" PRIu64 "\n", ts); + + int res = -1; /* Fail by default; */ + *elem = NULL; /* Empty list by default. */ + + int total = co_data->end - co_data->start + 1; + int num_objects = 0; /* Not found any object by now. */ + + /* First and last element of the list. */ + cobj_elem_t *first = NULL; + + int i; + for (i=0; iring[i]; + if (obj->assigned == true && obj->timestamp <= ts) { + /* Object found */ + + cobj_elem_t *elem_new = (cobj_elem_t*)pkg_malloc(sizeof(cobj_elem_t)); + if (!elem_new) { + LM_ERR("Memory error\n"); + goto clean; + } + + /* Fill new element with data */ + elem_new->number = co_data->start + i; + elem_new->timestamp = obj->timestamp; + elem_new->next = NULL; + elem_new->callid.s = (char*)pkg_malloc(obj->callid.len + 1); /* +1 Zero at the end */ + if (!elem_new->callid.s) { + LM_ERR("Cannot allocate memory for callid\n"); + goto clean; + } + memcpy(elem_new->callid.s, obj->callid.s, obj->callid.len); + elem_new->callid.s[obj->callid.len] = '\0'; + elem_new->callid.len = obj->callid.len; + + /* Insert the element in the ascending ordered list. */ + cobj_elem_t *previous = NULL; + cobj_elem_t *tmp = first; + while (tmp) { + if (elem_new->timestamp <= tmp->timestamp) { + /* We found the position of the new element. */ + break; + } + previous = tmp; + tmp = tmp->next; + } + + if (previous) { + /* Non-void list. */ + elem_new->next = previous->next; + previous->next = elem_new; + + } else { + /* Insert at the beginning. */ + elem_new->next = first; + first = elem_new; + } + num_objects++; + + /* Delete an element if we surpassed the limit. */ + if (limit && num_objects > limit) { + tmp = first; + first = first->next; + tmp->next = NULL; + cobj_free_list(tmp); + } + + } /* if obj->assigned */ + + } /* for i=0 */ + + /* Everything went fine */ + res = num_objects; + *elem = first; + first = NULL; + +clean: + if (first) { + /* An error occurred */ + cobj_free_list(first); + } + + return res; +} + +/** + * Free an object list. + * + * /param elem pointer to first element in the list. + */ +void cobj_free_list(cobj_elem_t *elem) +{ + while (elem) { + cobj_elem_t *next = elem->next; + if (elem->callid.s) { + pkg_free(elem->callid.s); + } + pkg_free(elem); + elem = next; + } +} diff --git a/src/modules/call_obj/cobj.h b/src/modules/call_obj/cobj.h new file mode 100644 index 00000000000..85aaab6a695 --- /dev/null +++ b/src/modules/call_obj/cobj.h @@ -0,0 +1,104 @@ +/** + * Header for functionality of Call Object module. + */ + +#ifndef _CALL_OBJ_H_ +#define _CALL_OBJ_H_ + +#include + +#include "../../core/str.h" + +/** + * Initialize call object module. + * + * /return 0 on success. + */ +int cobj_init(int c_start, int c_end); + +/** + * Close call object module. + */ +void cobj_destroy(void); + +/** + * Get a free object. + * + * /param timestamp assign this timestamp to the object we get. + * /param callid pointer to callid str. + * /return -1 if an error happens. + * /return number of a free object on success. + */ +int cobj_get(uint64_t timestamp, str *callid); + +/** + * Free an Object + * + * /param num number of object to free + * /return 0 on success + */ +int cobj_free(int num); + +/** + * Structure to store module statistics. + */ +typedef struct { + int start; + int end; + int assigned; +} cobj_stats_t; + +/** + * Fill data in cobj_stats_t structure passed as pointer. + * + * /param stats pointer to cobj_stats_t structure. + * /return 0 on success + */ +int cobj_stats_get(cobj_stats_t *stats); + +/** + * Free all objects at once. + */ +void cobj_free_all(void); + +/** + * Element of a returned object list. + */ +typedef struct _cobj_elem { + int number; + uint64_t timestamp; + str callid; + /* TODO */ + struct _cobj_elem *next; +} cobj_elem_t; + +/** + * Free an object list. + * + * /param elem pointer to first element in the list. + */ +void cobj_free_list(cobj_elem_t *elem); + +/** + * Get current timestamp in milliseconds. + * + * /param ts pointer to timestamp integer. + * /return 0 on success. + */ +int get_timestamp(uint64_t *ts); + +/** + * Get all objects which timestamp is less than or equals some value. + * + * User shall free returned list when not used any more. + * + * /param ts timestamp to compare. + * /param elem returned list. NULL on error of if zero elements. + * /param limit maximum number of objects to return. 0 means unlimited. + * + * /return number of returned objects on success. + * /return -1 on error + */ +int cobj_get_timestamp(uint64_t ts, cobj_elem_t **elem, int limit); + +#endif // _CALL_OBJ_H_ diff --git a/src/modules/call_obj/doc/Makefile b/src/modules/call_obj/doc/Makefile new file mode 100644 index 00000000000..8f57002254b --- /dev/null +++ b/src/modules/call_obj/doc/Makefile @@ -0,0 +1,4 @@ +docs = call_obj.xml + +docbook_dir = ../../../../doc/docbook +include $(docbook_dir)/Makefile.module diff --git a/src/modules/call_obj/doc/call_obj.xml b/src/modules/call_obj/doc/call_obj.xml new file mode 100644 index 00000000000..0da9a52a430 --- /dev/null +++ b/src/modules/call_obj/doc/call_obj.xml @@ -0,0 +1,41 @@ + + + +%docentities; + +]> + + + + CALL_OBJ Module + &kamailioname; + + + Vicente + Hernando + vhernando@systemonenoc.com + + + Vicente + Hernando + vhernando@systemonenoc.com + + + Javier + Gallart + jgallart@systemonenoc.com + + + + 2016 + www.systemonenoc.com + + + + + + + diff --git a/src/modules/call_obj/doc/call_obj_admin.xml b/src/modules/call_obj/doc/call_obj_admin.xml new file mode 100644 index 00000000000..a083144562f --- /dev/null +++ b/src/modules/call_obj/doc/call_obj_admin.xml @@ -0,0 +1,249 @@ + + + +%docentities; + +]> + + + + + &adminguide; + +
+ Overview + + This module provides a way to identify calls using a increasing sequence + of integers. + + + It starts assigning an integer to a call. Next call gets next free integer in a ring. + When a call finishes its assigned number shall be freed. + +
+ +
+ Dependencies +
+ &kamailio; Modules + + The following modules must be loaded before this module: + + + + none. + + + + +
+
+ External Libraries or Applications + + The following libraries or applications must be installed before running + &kamailio; with this module loaded: + + + + None. + + + + +
+
+
+ Parameters +
+ <varname>start</varname> (integer) + + First number to assign when no number has been assigned yet. + + + + This parameter has no default value. Not setting it raises an error. + + + Its value shall be greater than zero. + + Set <varname>start</varname> parameter + +... +modparam("call_obj", "start", 10) +... + + +
+
+ <varname>end</varname> (integer) + + Last number to assign when all other numbers have been already assigned. + + + + No default value. Not setting this parameter raises an error. + + + Its value shall be greater than zero. + + Set <varname>end</varname> parameter + +... +modparam("call_obj", "end", 93) +... + + +
+
+ +
+ Functions +
+ + <function moreinfo="none">call_obj_get(reply_number)</function> + + + Get next free number. reply_number parameter is a variable where function will + store result in string format. + + + <function>call_obj_get</function> usage + +... +if (call_obj_get("$dlg_var(x)")) { + xlog("Object: $dlg_var(x)\n"); +} +... + + +
+
+ + <function moreinfo="none">call_obj_free(object_number)</function> + + + Mark an object_number as free, so it can be assigned again. + This number will not be readily assigned until a loop in the ring has completed. + object_number shall be provided in string format. + + + <function>call_obj_free</function> usage + +... +$dlg_var(y) = "27"; +if (call_obj_free("$dlg_var(y)")) { + xlog("object $dlg_var(y) freed OK\n"); +} +... + + +
+
+ +
+ <acronym>RPC</acronym> Commands +
+ <function moreinfo="none">call_obj.free</function> + + Free an object. + + + Parameter is a number represented in string format. + + + Name: call_obj.free + + Parameters: object_number + + <function>call_obj.free</function> usage + +... +&sercmd; call_obj.free s:12 +... + + +
+
+ <function moreinfo="none">call_obj.stats</function> + + Return some statistics about call_obj module. + + + It currently returns: + + + Start: Number of first object + + + End: Number of last object in range + + + Total: Total assignable objects + + + Assigned: Number of currently assigned objects + + + + + Name: call_obj.stats + + Parameters: none + + <function>call_obj.stats</function> usage + +... +&sercmd; call_obj.stats +... + + +
+
+ <function moreinfo="none">call_obj.free_all</function> + + Make all objects free at once. + + + Name: call_obj.free_all + + Parameters: none + + <function>call_obj.free_all</function> usage + +... +&sercmd; call_obj.free_all +... + + +
+
+ <function moreinfo="none">call_obj.list</function> + + List all active calls which duration is longer than a value in seconds. + + + Name: call_obj.list + + Parameters: duration limit + limit parameter is optional + + <function>call_obj.list</function> usage + +... +Show every call which duration is less than or equal to 24 seconds: +&sercmd; call_obj.list 24 + +Same but limit output to at most 5 calls: +&sercmd; call_obj.list 24 5 + +Same but show again all calls (0 means no limit): +&sercmd; call_obj.list 24 0 +... + + +
+
+