Browse Source

call_obj: module to track calls and its duration by using ascending integers.

Vicente Hernando 8 years ago
parent
commit
0bded933d8

+ 10 - 0
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

+ 247 - 0
src/modules/call_obj/README

@@ -0,0 +1,247 @@
+CALL_OBJ Module
+
+Vicente Hernando
+
+   <[email protected]>
+
+Edited by
+
+Vicente Hernando
+
+   <[email protected]>
+
+Javier Gallart
+
+   <[email protected]>
+
+   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
+...

+ 366 - 0
src/modules/call_obj/call_obj_mod.c

@@ -0,0 +1,366 @@
+
+#include <inttypes.h>
+
+#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(&current_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(&current_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;
+}

+ 510 - 0
src/modules/call_obj/cobj.c

@@ -0,0 +1,510 @@
+/**
+ * Functionality of call_obj module.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/time.h>
+
+#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; i<total_size; i++) {
+		co_data->ring[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(&current_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; i<total; i++) {
+		co_object_t *obj = &co_data->ring[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;
+	}
+}

+ 104 - 0
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 <stdint.h>
+
+#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_

+ 4 - 0
src/modules/call_obj/doc/Makefile

@@ -0,0 +1,4 @@
+docs = call_obj.xml
+
+docbook_dir = ../../../../doc/docbook
+include $(docbook_dir)/Makefile.module

+ 41 - 0
src/modules/call_obj/doc/call_obj.xml

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+	<bookinfo>
+		<title>CALL_OBJ Module</title>
+		<productname class="trade">&kamailioname;</productname>
+		<authorgroup>
+			<author>
+				<firstname>Vicente</firstname>
+				<surname>Hernando</surname>
+				<email>[email protected]</email>
+			</author>
+			<editor>
+				<firstname>Vicente</firstname>
+				<surname>Hernando</surname>
+				<email>[email protected]</email>
+			</editor>
+			<author>
+			    <firstname>Javier</firstname>
+				<surname>Gallart</surname>
+				<email>[email protected]</email>
+			</author>
+		</authorgroup>
+		<copyright>
+			<year>2016</year>
+			<holder>www.systemonenoc.com</holder>
+		</copyright>
+	</bookinfo>
+	<toc></toc>
+
+	<xi:include href="call_obj_admin.xml"/>
+
+</book>

+ 249 - 0
src/modules/call_obj/doc/call_obj_admin.xml

@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding='ISO-8859-1'?>
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
+"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
+
+<!-- Include general documentation entities -->
+<!ENTITY % docentities SYSTEM "../../../../doc/docbook/entities.xml">
+%docentities;
+
+]>
+<!-- Module User's Guide -->
+
+<chapter>
+
+	<title>&adminguide;</title>
+
+	<section>
+	<title>Overview</title>
+	<para>
+		This module provides a way to identify calls using a increasing sequence
+		of integers.
+	</para>
+	<para>
+		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.
+	</para>
+	</section>
+
+	<section>
+	<title>Dependencies</title>
+	<section>
+		<title>&kamailio; Modules</title>
+		<para>
+		The following modules must be loaded before this module:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>none</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	<section>
+		<title>External Libraries or Applications</title>
+		<para>
+		The following libraries or applications must be installed before running
+		&kamailio; with this module loaded:
+			<itemizedlist>
+			<listitem>
+			<para>
+				<emphasis>None</emphasis>.
+			</para>
+			</listitem>
+			</itemizedlist>
+		</para>
+	</section>
+	</section>
+	<section>
+	<title>Parameters</title>
+	<section id="call_obj.p.start">
+		<title><varname>start</varname> (integer)</title>
+		<para>
+			First number to assign when no number has been assigned yet.
+		</para>
+		<para>
+			<emphasis>
+			This parameter has no default value. Not setting it raises an error.
+			</emphasis>
+		</para>
+		<para>Its value shall be greater than zero.</para>
+		<example>
+			<title>Set <varname>start</varname> parameter</title>
+			<programlisting format="linespecific">
+...
+modparam("call_obj", "start", 10)
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="call_obj.p.end">
+		<title><varname>end</varname> (integer)</title>
+		<para>
+			Last number to assign when all other numbers have been already assigned.
+		</para>
+		<para>
+		<emphasis>
+			No default value. Not setting this parameter raises an error.
+		</emphasis>
+		</para>
+		<para>Its value shall be greater than zero.</para>		
+		<example>
+			<title>Set <varname>end</varname> parameter</title>
+			<programlisting format="linespecific">
+...
+modparam("call_obj", "end", 93)
+...
+			</programlisting>
+		</example>
+	</section>
+	</section>
+
+	<section>
+	<title>Functions</title>
+	<section id="call_obj.f.call_obj_get">
+		<title>
+		<function moreinfo="none">call_obj_get(reply_number)</function>
+		</title>
+		<para>
+			Get next free number. reply_number parameter is a variable where function will
+			store result in string format.
+		</para>
+		<example>
+		<title><function>call_obj_get</function> usage</title>
+		<programlisting format="linespecific">
+...
+if (call_obj_get("$dlg_var(x)")) {
+	xlog("Object: $dlg_var(x)\n");
+}
+...
+</programlisting>
+		</example>
+	</section>
+	<section id="call_obj.f.call_obj_free">
+	<title>
+		<function moreinfo="none">call_obj_free(object_number)</function>
+	</title>
+	<para>
+		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.
+	</para>
+	<example>
+		<title><function>call_obj_free</function> usage</title>
+		<programlisting format="linespecific">
+...
+$dlg_var(y) = "27";
+if (call_obj_free("$dlg_var(y)")) {
+	xlog("object $dlg_var(y) freed OK\n");
+}
+...
+		</programlisting>
+	</example>
+	</section>
+	</section>
+
+	<section>
+		<title><acronym>RPC</acronym> Commands</title>
+		<section  id="call_obj.rpc.free">
+			<title><function moreinfo="none">call_obj.free</function></title>
+			<para>
+			  Free an object.
+			</para>
+			<para>
+			  Parameter is a number represented in string format.
+			</para>
+            <para>
+            Name: <emphasis>call_obj.free</emphasis>
+            </para>
+            <para>Parameters: <emphasis>object_number</emphasis></para>
+			<example>
+			  <title><function>call_obj.free</function> usage</title>
+			  <programlisting format="linespecific">
+...
+&sercmd; call_obj.free s:12
+...
+			  </programlisting>
+			</example>
+        </section>
+		<section  id="call_obj.rpc.stats">
+			<title><function moreinfo="none">call_obj.stats</function></title>
+			<para>
+			  Return some statistics about call_obj module.
+			</para>
+			<para>
+			  It currently returns:
+			  <itemizedlist>
+				<listitem>
+				  <para><emphasis>Start</emphasis>: Number of first object</para>
+				</listitem>
+				<listitem>
+				  <para><emphasis>End</emphasis>: Number of last object in range</para>
+				</listitem>
+				<listitem>
+				  <para><emphasis>Total</emphasis>: Total assignable objects</para>
+				</listitem>
+				<listitem>
+				  <para><emphasis>Assigned</emphasis>: Number of currently assigned objects</para>
+				</listitem>
+			  </itemizedlist>
+			</para>
+            <para>
+            Name: <emphasis>call_obj.stats</emphasis>
+            </para>
+            <para>Parameters: <emphasis>none</emphasis></para>
+			<example>
+			  <title><function>call_obj.stats</function> usage</title>
+			  <programlisting format="linespecific">
+...
+&sercmd; call_obj.stats
+...
+			  </programlisting>
+			</example>
+        </section>
+		<section  id="call_obj.rpc.free_all">
+			<title><function moreinfo="none">call_obj.free_all</function></title>
+			<para>
+			  Make all objects free at once.
+			</para>
+            <para>
+            Name: <emphasis>call_obj.free_all</emphasis>
+            </para>
+            <para>Parameters: <emphasis>none</emphasis></para>
+			<example>
+			  <title><function>call_obj.free_all</function> usage</title>
+			  <programlisting format="linespecific">
+...
+&sercmd; call_obj.free_all
+...
+			  </programlisting>
+			</example>
+        </section>
+		<section  id="call_obj.rpc.list">
+			<title><function moreinfo="none">call_obj.list</function></title>
+			<para>
+			  List all active calls which duration is longer than a value in seconds.
+			</para>
+            <para>
+            Name: <emphasis>call_obj.list</emphasis>
+            </para>
+            <para>Parameters: <emphasis>duration</emphasis> <emphasis>limit</emphasis></para>
+			<para>limit parameter is optional</para>
+			<example>
+			  <title><function>call_obj.list</function> usage</title>
+			  <programlisting format="linespecific">
+...
+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
+...
+			  </programlisting>
+			</example>
+        </section>
+	</section>
+</chapter>