src/modules/cnxcc/cnxcc_mod.c
96596e28
 /*
  * Copyright (C) 2012 Carlos Ruiz Díaz (caruizdiaz.com),
  *                    ConexionGroup (www.conexiongroup.com)
  *
  * This file is part of Kamailio, a free SIP server.
  *
  * Kamailio is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
  * the Free Software Foundation; either version 2 of the License, or
  * (at your option) any later version
  *
  * Kamailio is distributed in the hope that it will be useful,
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, write to the Free Software
c587ce0b
  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
96596e28
  *
  */
 
 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
 #include <ctype.h>
 
cf83221d
 #include "../../core/sr_module.h"
 #include "../../core/dprint.h"
 #include "../../core/error.h"
 #include "../../core/mem/mem.h"
 #include "../../core/shm_init.h"
 #include "../../core/mem/shm_mem.h"
 #include "../../core/pvar.h"
 #include "../../core/locking.h"
 #include "../../core/lock_ops.h"
 #include "../../core/str_hash.h"
 #include "../../core/timer_proc.h"
96596e28
 #include "../../modules/tm/tm_load.h"
cf83221d
 #include "../../core/parser/parse_from.h"
 #include "../../core/parser/parse_to.h"
 #include "../../core/parser/parse_uri.h"
 #include "../../core/parser/parse_cseq.h"
 #include "../../core/parser/contact/parse_contact.h"
 #include "../../core/parser/contact/contact.h"
 #include "../../core/parser/parse_rr.h"
 #include "../../core/mod_fix.h"
96596e28
 #include "../dialog/dlg_load.h"
 #include "../dialog/dlg_hash.h"
cf83221d
 #include "../../core/fmsg.h"
 #include "../../core/rpc.h"
 #include "../../core/rpc_lookup.h"
b1899d28
 #include "../../core/kemi.h"
96596e28
 
 #include "cnxcc_mod.h"
 #include "cnxcc.h"
 #include "cnxcc_sip_msg_faker.h"
 #include "cnxcc_check.h"
 #include "cnxcc_rpc.h"
fc83d3b1
 #include "cnxcc_select.h"
c587ce0b
 #include "cnxcc_redis.h"
96596e28
 
 MODULE_VERSION
 
c587ce0b
 #define HT_SIZE 229
db004a73
 #define MODULE_NAME "cnxcc"
c587ce0b
 #define NUMBER_OF_TIMERS 2
96596e28
 
db004a73
 #define TRUE 1
 #define FALSE !TRUE
96596e28
 
 data_t _data;
 struct dlg_binds _dlgbinds;
 
41953f19
 static int cnxcc_set_max_credit_fixup(void **param, int param_no);
96596e28
 
 /*
  *  module core functions
  */
c587ce0b
 static int __mod_init(void);
 static int __child_init(int);
 static int __init_hashtable(struct str_hash_table *ht);
96596e28
 
 /*
  * Memory management functions
  */
c587ce0b
 static int __shm_str_hash_alloc(struct str_hash_table *ht, int size);
 static void __free_credit_data_hash_entry(struct str_hash_entry *e);
96596e28
 
 /*
  * PV management functions
  */
c587ce0b
 static int __pv_parse_calls_param(pv_spec_p sp, str *in);
db004a73
 static int __pv_get_calls(
 		struct sip_msg *msg, pv_param_t *param, pv_value_t *res);
96596e28
 
 /*
  * Billing management functions
  */
c64518a2
 static int __set_max_credit(sip_msg_t *msg, char *pclient, char *pcredit,
 		char *pconnect, char *pcps, char *pinitp, char *pfinishp);
41953f19
 static int __set_max_time(sip_msg_t *msg, char *pclient, char *pmaxsecs);
 static int __update_max_time(sip_msg_t *msg, char *pclient, char *psecs);
 static int __set_max_channels(sip_msg_t *msg, char *pclient, char *pmaxchan);
 static int __get_channel_count(sip_msg_t *msg, char *pclient, char *pcount);
 static int __terminate_all(sip_msg_t *msg, char *pclient, char *p2);
db004a73
 
 static void __start_billing(
 		str *callid, str *from_uri, str *to_uri, str tags[2]);
 static void __setup_billing(
 		str *callid, unsigned int h_entry, unsigned int h_id);
c587ce0b
 static void __stop_billing(str *callid);
 static int __add_call_by_cid(str *cid, call_t *call, credit_type_t type);
db004a73
 static call_t *__alloc_new_call_by_time(
 		credit_data_t *credit_data, struct sip_msg *msg, int max_secs);
 static call_t *__alloc_new_call_by_money(credit_data_t *credit_data,
c64518a2
 		struct sip_msg *msg, double credit, double connect_cost,
 		double cost_per_second, int initial_pulse, int final_pulse);
6177e766
 static void __notify_call_termination(sip_msg_t *msg);
c587ce0b
 static void __free_call(call_t *call);
 static int __has_to_tag(struct sip_msg *msg);
db004a73
 static credit_data_t *__alloc_new_credit_data(
 		str *client_id, credit_type_t type);
 static credit_data_t *__get_or_create_credit_data_entry(
 		str *client_id, credit_type_t type);
96596e28
 
 /*
d2e3b7c8
  * control interface
96596e28
  */
db004a73
 void rpc_credit_control_stats(rpc_t *rpc, void *ctx);
96596e28
 
 /*
  * Dialog management callback functions
  */
db004a73
 static void __dialog_terminated_callback(
 		struct dlg_cell *cell, int type, struct dlg_cb_params *params);
 static void __dialog_confirmed_callback(
 		struct dlg_cell *cell, int type, struct dlg_cb_params *params);
 static void __dialog_created_callback(
 		struct dlg_cell *cell, int type, struct dlg_cb_params *params);
 
 /* clang-format off */
c587ce0b
 static pv_export_t mod_pvs[] = {
db004a73
 	{ {"cnxcc", sizeof("cnxcc")-1 }, PVT_OTHER, __pv_get_calls, 0,
 			__pv_parse_calls_param, 0, 0, 0 },
96596e28
 	{ {0, 0}, 0, 0, 0, 0, 0, 0, 0 }
 };
 
c587ce0b
 static cmd_export_t cmds[] = {
c64518a2
 	{"cnxcc_set_max_credit", (cmd_function) __set_max_credit, 6,
41953f19
 		cnxcc_set_max_credit_fixup, NULL, ANY_ROUTE},
db004a73
 	{"cnxcc_set_max_time", (cmd_function) __set_max_time, 2,
41953f19
 		fixup_spve_igp, fixup_free_spve_igp, ANY_ROUTE},
db004a73
 	{"cnxcc_update_max_time", (cmd_function) __update_max_time, 2,
41953f19
 		fixup_spve_igp, fixup_free_spve_igp, ANY_ROUTE},
db004a73
 	{"cnxcc_set_max_channels", (cmd_function) __set_max_channels, 2,
41953f19
 		fixup_spve_igp, fixup_free_spve_igp, ANY_ROUTE},
db004a73
 	{"cnxcc_get_channel_count", (cmd_function) __get_channel_count, 2,
41953f19
 		fixup_spve_pvar, fixup_free_spve_pvar, ANY_ROUTE},
db004a73
 	{"cnxcc_terminate_all", (cmd_function) __terminate_all, 1,
41953f19
 		fixup_spve_null, fixup_free_spve_null, ANY_ROUTE},
96596e28
 	{0,0,0,0,0,0}
 };
 
c587ce0b
 static param_export_t params[] = {
 	{"dlg_flag", INT_PARAM,	&_data.ctrl_flag },
 	{"credit_check_period", INT_PARAM,	&_data.check_period },
 	{"redis", STR_PARAM, &_data.redis_cnn_str.s },
96596e28
 	{ 0, 0, 0 }
 };
db004a73
 /* clang-format on */
96596e28
 
db004a73
 static const char *rpc_active_clients_doc[2] = {
 		"List of clients with active calls", NULL};
96596e28
 
db004a73
 static const char *rpc_check_client_stats_doc[2] = {
 		"Check specific client calls", NULL};
96596e28
 
db004a73
 static const char *rpc_kill_call_doc[2] = {"Kill call using its call ID", NULL};
d2e3b7c8
 
db004a73
 static const char *rpc_credit_control_stats_doc[2] = {
 		"List credit control stats", NULL};
d2e3b7c8
 
db004a73
 /* clang-format off */
d2e3b7c8
 rpc_export_t cnxcc_rpc[] = {
db004a73
 	{ "cnxcc.active_clients", rpc_active_clients, rpc_active_clients_doc,	0},
 	{ "cnxcc.check_client", rpc_check_client_stats, rpc_check_client_stats_doc,	0},
 	{ "cnxcc.kill_call", rpc_kill_call, rpc_kill_call_doc, 0},
d2e3b7c8
 	{ "cnxcc.stats", rpc_credit_control_stats, rpc_credit_control_stats_doc, 0},
db004a73
 	{ NULL, NULL, NULL, 0}
96596e28
 };
 
fc83d3b1
 /* selects declaration */
 select_row_t sel_declaration[] = {
db004a73
 	{ NULL, SEL_PARAM_STR, STR_STATIC_INIT("cnxcc"), sel_root,
 		SEL_PARAM_EXPECTED},
 	{ sel_root, SEL_PARAM_STR, STR_STATIC_INIT("channels"), sel_channels,
 		SEL_PARAM_EXPECTED|CONSUME_NEXT_STR|FIXUP_CALL},
 	{ sel_channels, SEL_PARAM_STR, STR_STATIC_INIT("count"), sel_channels_count,
 		0},
 	{ NULL, SEL_PARAM_STR, STR_NULL, NULL, 0}
fc83d3b1
 };
 
96596e28
 /** module exports */
c587ce0b
 struct module_exports exports = {
c2e9d850
 	MODULE_NAME,     /* module name */
c587ce0b
 	DEFAULT_DLFLAGS, /* dlopen flags */
c2e9d850
 	cmds,            /* cmd (cfg function) exports */
 	params,          /* param exports */
 	0,               /* RPC method exports */
 	mod_pvs,         /* pseudo-variables exports */
 	0,               /* response handling function */
 	__mod_init,      /* module init function */
 	__child_init,    /* per-child init function */
 	0                /* module destroy function */
96596e28
 };
db004a73
 /* clang-format on */
96596e28
 
41953f19
 static int cnxcc_set_max_credit_fixup(void **param, int param_no)
db004a73
 {
41953f19
 	switch(param_no) {
 		case 1:
 		case 2:
 		case 3:
 			return fixup_spve_all(param, param_no);
 		case 4:
c64518a2
 			return fixup_spve_all(param, param_no);
41953f19
 		case 5:
c64518a2
 		case 6:
41953f19
 			return fixup_igp_all(param, param_no);
 		default:
 			LM_ERR("unexpected parameter number: %d\n", param_no);
 			return E_CFG;
96596e28
 	}
 }
 
db004a73
 static int __mod_init(void)
 {
c587ce0b
 	int len;
 	char *chr;
 
fc83d3b1
 	LM_INFO("Loading " MODULE_NAME " module\n");
96596e28
 
 	_data.cs_route_number = route_get(&event_rt, "cnxcc:call-shutdown");
 
db004a73
 	if(_data.cs_route_number < 0)
516eee4a
 		LM_INFO("No cnxcc:call-shutdown event route found\n");
96596e28
 
db004a73
 	if(_data.cs_route_number > 0
 			&& event_rt.rlist[_data.cs_route_number] == NULL) {
516eee4a
 		LM_INFO("cnxcc:call-shutdown route is empty\n");
db004a73
 		_data.cs_route_number = -1;
96596e28
 	}
 
db004a73
 	if(_data.check_period <= 0) {
516eee4a
 		LM_INFO("credit_check_period cannot be less than 1 second\n");
96596e28
 		return -1;
 	}
 
db004a73
 	if(_data.redis_cnn_str.s)
c587ce0b
 		_data.redis_cnn_str.len = strlen(_data.redis_cnn_str.s);
 
db004a73
 	_data.time.credit_data_by_client =
 			shm_malloc(sizeof(struct str_hash_table));
c587ce0b
 	_data.time.call_data_by_cid = shm_malloc(sizeof(struct str_hash_table));
db004a73
 	_data.money.credit_data_by_client =
 			shm_malloc(sizeof(struct str_hash_table));
c587ce0b
 	_data.money.call_data_by_cid = shm_malloc(sizeof(struct str_hash_table));
db004a73
 	_data.channel.credit_data_by_client =
 			shm_malloc(sizeof(struct str_hash_table));
c587ce0b
 	_data.channel.call_data_by_cid = shm_malloc(sizeof(struct str_hash_table));
96596e28
 
d0bdaa75
 	memset(_data.time.credit_data_by_client, 0, sizeof(struct str_hash_table));
 	memset(_data.time.call_data_by_cid, 0, sizeof(struct str_hash_table));
 	memset(_data.money.credit_data_by_client, 0, sizeof(struct str_hash_table));
 	memset(_data.money.call_data_by_cid, 0, sizeof(struct str_hash_table));
db004a73
 	memset(_data.channel.credit_data_by_client, 0,
 			sizeof(struct str_hash_table));
d0bdaa75
 	memset(_data.channel.call_data_by_cid, 0, sizeof(struct str_hash_table));
 
db004a73
 	_data.stats = (stats_t *)shm_malloc(sizeof(stats_t));
96596e28
 
db004a73
 	if(!_data.stats) {
96596e28
 		LM_ERR("Error allocating shared memory stats\n");
 		return -1;
 	}
 
c587ce0b
 	_data.stats->active = 0;
 	_data.stats->dropped = 0;
 	_data.stats->total = 0;
96596e28
 
db004a73
 	if(__init_hashtable(_data.time.credit_data_by_client) != 0)
96596e28
 		return -1;
 
db004a73
 	if(__init_hashtable(_data.time.call_data_by_cid) != 0)
96596e28
 		return -1;
 
db004a73
 	if(__init_hashtable(_data.money.credit_data_by_client) != 0)
96596e28
 		return -1;
 
db004a73
 	if(__init_hashtable(_data.money.call_data_by_cid) != 0)
96596e28
 		return -1;
 
db004a73
 	if(__init_hashtable(_data.channel.credit_data_by_client) != 0)
fc83d3b1
 		return -1;
 
db004a73
 	if(__init_hashtable(_data.channel.call_data_by_cid) != 0)
fc83d3b1
 		return -1;
 
a4cdd61a
 
 	cnxcc_lock_init(_data.lock);
 
 	cnxcc_lock_init(_data.time.lock);
 	cnxcc_lock_init(_data.money.lock);
 	cnxcc_lock_init(_data.channel.lock);
96596e28
 
 	/*
 	 * One for time based monitoring
 	 * One for money based monitoring
 	 */
 	register_dummy_timers(NUMBER_OF_TIMERS);
 
db004a73
 	if(rpc_register_array(cnxcc_rpc) != 0) {
96596e28
 		LM_ERR("Failed registering RPC commands\n");
 		return -1;
 	}
 
db004a73
 	if(load_dlg_api(&_dlgbinds) != 0) {
96596e28
 		LM_ERR("Error loading dialog API\n");
db004a73
 		return -1;
96596e28
 	}
 
db004a73
 	_dlgbinds.register_dlgcb(
 			NULL, DLGCB_CREATED, __dialog_created_callback, NULL, NULL);
96596e28
 
c587ce0b
 	register_select_table(sel_declaration);
 
 	// redis configuration setup
db004a73
 	if(_data.redis_cnn_str.len <= 0)
c587ce0b
 		return 0;
 
 	// replace ";" for " ", so we can use a simpler pattern in sscanf()
 	for(chr = _data.redis_cnn_str.s; *chr; chr++)
db004a73
 		if(*chr == ';')
c587ce0b
 			*chr = ' ';
 
 	memset(_data.redis_cnn_info.host, 0, sizeof(_data.redis_cnn_info.host));
db004a73
 	sscanf(_data.redis_cnn_str.s, "addr=%s port=%d db=%d",
 			_data.redis_cnn_info.host, &_data.redis_cnn_info.port,
 			&_data.redis_cnn_info.db);
c587ce0b
 
 	len = strlen(_data.redis_cnn_info.host);
 	//
 	// Redis modparam validations
 	//
db004a73
 	if(len == 0) {
c587ce0b
 		LM_ERR("invalid host address [%s]", _data.redis_cnn_info.host);
 		return -1;
 	}
 
db004a73
 	if(_data.redis_cnn_info.port <= 0) {
c587ce0b
 		LM_ERR("invalid port number [%d]", _data.redis_cnn_info.port);
 		return -1;
 	}
 
db004a73
 	if(_data.redis_cnn_info.db < 0) {
 		LM_ERR("invalid db number [%d]", _data.redis_cnn_info.db);
c587ce0b
 		return -1;
 	}
fc83d3b1
 
db004a73
 	LM_INFO("Redis connection info: ip=[%s], port=[%d], database=[%d]",
 			_data.redis_cnn_info.host, _data.redis_cnn_info.port,
 			_data.redis_cnn_info.db);
c587ce0b
 
db004a73
 	register_procs(3 /* 2 timers + 1 redis async receiver */);
96596e28
 	return 0;
 }
 
db004a73
 static int __child_init(int rank)
 {
c587ce0b
 	int pid = 0;
 
db004a73
 	if(rank != PROC_INIT && rank != PROC_MAIN && rank != PROC_TCP_MAIN) {
 		if(_data.redis_cnn_str.len <= 0)
c587ce0b
 			return 0;
 
 		_data.redis = redis_connect(_data.redis_cnn_info.host,
db004a73
 				_data.redis_cnn_info.port, _data.redis_cnn_info.db);
c587ce0b
 		return (!_data.redis) ? -1 : 0;
 	}
 
db004a73
 	if(rank != PROC_MAIN)
96596e28
 		return 0;
 
db004a73
 	if(fork_dummy_timer(PROC_TIMER, "CNXCC TB TIMER", 1, check_calls_by_money,
 			   NULL, _data.check_period)
 			< 0) {
c587ce0b
 		LM_ERR("Failed registering TB TIMER routine as process\n");
 		return -1;
 	}
96596e28
 
db004a73
 	if(fork_dummy_timer(PROC_TIMER, "CNXCC MB TIMER", 1, check_calls_by_time,
 			   NULL, _data.check_period)
 			< 0) {
c587ce0b
 		LM_ERR("Failed registering MB TIMER routine as process\n");
96596e28
 		return -1;
 	}
 
db004a73
 	if(_data.redis_cnn_str.len <= 0)
c587ce0b
 		return 0;
 
 
 	pid = fork_process(PROC_NOCHLDINIT, "Redis Async receiver", 1);
 
db004a73
 	if(pid < 0) {
c587ce0b
 		LM_ERR("error forking Redis receiver\n");
96596e28
 		return -1;
db004a73
 	} else if(pid == 0) {
c587ce0b
 		_data.redis = redis_connect_async(_data.redis_cnn_info.host,
db004a73
 				_data.redis_cnn_info.port, _data.redis_cnn_info.db);
c587ce0b
 
db004a73
 		return (!_data.redis) ? -1 : 0;
 		;
c587ce0b
 	}
96596e28
 
 	return 0;
 }
 
db004a73
 static int __init_hashtable(struct str_hash_table *ht)
 {
 	if(__shm_str_hash_alloc(ht, HT_SIZE) != 0) {
96596e28
 		LM_ERR("Error allocating shared memory hashtable\n");
 		return -1;
 	}
 
 	str_hash_init(ht);
 	return 0;
 }
 
db004a73
 static void __dialog_created_callback(
 		struct dlg_cell *cell, int type, struct dlg_cb_params *params)
 {
c587ce0b
 	struct sip_msg *msg = NULL;
96596e28
 
c587ce0b
 	msg = params->direction == SIP_REPLY ? params->rpl : params->req;
96596e28
 
db004a73
 	if(msg == NULL) {
96596e28
 		LM_ERR("Error getting direction of SIP msg\n");
 		return;
 	}
 
db004a73
 	if(isflagset(msg, _data.ctrl_flag) == -1) {
96596e28
 		LM_DBG("Flag is not set for this message. Ignoring\n");
 		return;
 	}
 
516eee4a
 	LM_DBG("Dialog created for CID [%.*s]\n", cell->callid.len, cell->callid.s);
96596e28
 
db004a73
 	_dlgbinds.register_dlgcb(
 			cell, DLGCB_CONFIRMED, __dialog_confirmed_callback, NULL, NULL);
 	_dlgbinds.register_dlgcb(cell,
 			DLGCB_TERMINATED | DLGCB_FAILED | DLGCB_EXPIRED,
 			__dialog_terminated_callback, NULL, NULL);
96596e28
 
c587ce0b
 	__setup_billing(&cell->callid, cell->h_entry, cell->h_id);
96596e28
 }
 
db004a73
 static void __dialog_confirmed_callback(
 		struct dlg_cell *cell, int type, struct dlg_cb_params *params)
 {
 	LM_DBG("Dialog confirmed for CID [%.*s]\n", cell->callid.len,
 			cell->callid.s);
96596e28
 
c587ce0b
 	__start_billing(&cell->callid, &cell->from_uri, &cell->to_uri, cell->tag);
96596e28
 }
 
db004a73
 static void __dialog_terminated_callback(
 		struct dlg_cell *cell, int type, struct dlg_cb_params *params)
 {
 	LM_DBG("Dialog terminated for CID [%.*s]\n", cell->callid.len,
 			cell->callid.s);
96596e28
 
c587ce0b
 	__stop_billing(&cell->callid);
96596e28
 }
 
db004a73
 static void __notify_call_termination(sip_msg_t *msg)
 {
96596e28
 	struct run_act_ctx ra_ctx;
 
 	init_run_actions_ctx(&ra_ctx);
 	//run_top_route(event_rt.rlist[_data.cs_route_number], msg, &ra_ctx);
 
db004a73
 	if(run_actions(&ra_ctx, event_rt.rlist[_data.cs_route_number], msg) < 0)
516eee4a
 		LM_ERR("Error executing cnxcc:call-shutdown route\n");
96596e28
 }
 
db004a73
 int try_get_credit_data_entry(str *client_id, credit_data_t **credit_data)
 {
 	struct str_hash_entry *cd_entry = NULL;
c587ce0b
 	hash_tables_t *hts = NULL;
 	*credit_data = NULL;
96596e28
 
fc83d3b1
 	/* by money */
c587ce0b
 	hts = &_data.money;
a4cdd61a
 	cnxcc_lock(hts->lock);
96596e28
 
db004a73
 	cd_entry = str_hash_get(
 			hts->credit_data_by_client, client_id->s, client_id->len);
96596e28
 
db004a73
 	if(cd_entry != NULL) {
 		*credit_data = cd_entry->u.p;
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 		return 0;
 	}
 
a4cdd61a
 	cnxcc_unlock(hts->lock);
96596e28
 
fc83d3b1
 	/* by time */
c587ce0b
 	hts = &_data.time;
a4cdd61a
 	cnxcc_lock(hts->lock);
96596e28
 
db004a73
 	cd_entry = str_hash_get(
 			hts->credit_data_by_client, client_id->s, client_id->len);
fc83d3b1
 
db004a73
 	if(cd_entry != NULL) {
 		*credit_data = cd_entry->u.p;
a4cdd61a
 		cnxcc_unlock(hts->lock);
fc83d3b1
 		return 0;
 	}
 
a4cdd61a
 	cnxcc_unlock(hts->lock);
fc83d3b1
 
 	/* by channel */
c587ce0b
 	hts = &_data.channel;
a4cdd61a
 	cnxcc_lock(hts->lock);
fc83d3b1
 
db004a73
 	cd_entry = str_hash_get(
 			hts->credit_data_by_client, client_id->s, client_id->len);
96596e28
 
db004a73
 	if(cd_entry != NULL) {
 		*credit_data = cd_entry->u.p;
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 		return 0;
 	}
 
a4cdd61a
 	cnxcc_unlock(hts->lock);
96596e28
 	return -1;
 }
 
db004a73
 int try_get_call_entry(str *callid, call_t **call, hash_tables_t **hts)
 {
c587ce0b
 	struct str_hash_entry *call_entry = NULL;
 
 	*call = NULL;
96596e28
 
fc83d3b1
 	/* by money */
c587ce0b
 	*hts = &_data.money;
a4cdd61a
 	cnxcc_lock((*hts)->lock);
96596e28
 
c587ce0b
 	call_entry = str_hash_get((*hts)->call_data_by_cid, callid->s, callid->len);
96596e28
 
db004a73
 	if(call_entry != NULL) {
c587ce0b
 		*call = call_entry->u.p;
a4cdd61a
 		cnxcc_unlock((*hts)->lock);
96596e28
 		return 0;
 	}
 
a4cdd61a
 	cnxcc_unlock((*hts)->lock);
96596e28
 
fc83d3b1
 	/* by time */
c587ce0b
 	*hts = &_data.time;
a4cdd61a
 	cnxcc_lock((*hts)->lock);
96596e28
 
c587ce0b
 	call_entry = str_hash_get((*hts)->call_data_by_cid, callid->s, callid->len);
96596e28
 
db004a73
 	if(call_entry != NULL) {
c587ce0b
 		*call = call_entry->u.p;
a4cdd61a
 		cnxcc_unlock((*hts)->lock);
96596e28
 		return 0;
 	}
 
a4cdd61a
 	cnxcc_unlock((*hts)->lock);
96596e28
 
fc83d3b1
 	/* by channel */
c587ce0b
 	*hts = &_data.channel;
a4cdd61a
 	cnxcc_lock((*hts)->lock);
fc83d3b1
 
c587ce0b
 	call_entry = str_hash_get((*hts)->call_data_by_cid, callid->s, callid->len);
fc83d3b1
 
db004a73
 	if(call_entry != NULL) {
c587ce0b
 		*call = call_entry->u.p;
a4cdd61a
 		cnxcc_unlock((*hts)->lock);
fc83d3b1
 		return 0;
 	}
 
a4cdd61a
 	cnxcc_unlock((*hts)->lock);
96596e28
 	return -1;
 }
 
db004a73
 static void __stop_billing(str *callid)
 {
 	struct str_hash_entry *cd_entry = NULL;
 	call_t *call = NULL;
 	hash_tables_t *hts = NULL;
 	credit_data_t *credit_data = NULL;
96596e28
 
 	/*
 	 * Search call data by call-id
 	 */
db004a73
 	if(try_get_call_entry(callid, &call, &hts) != 0) {
516eee4a
 		LM_ERR("Call [%.*s] not found\n", callid->len, callid->s);
96596e28
 		return;
 	}
 
db004a73
 	if(call == NULL) {
516eee4a
 		LM_ERR("[%.*s] call pointer is null\n", callid->len, callid->s);
96596e28
 		return;
 	}
 
db004a73
 	if(hts == NULL) {
 		LM_ERR("[%.*s] result hashtable pointer is null\n", callid->len,
 				callid->s);
96596e28
 		return;
 	}
 
a4cdd61a
 	cnxcc_lock(hts->lock);
c587ce0b
 
96596e28
 	/*
 	 * Search credit_data by client_id
 	 */
db004a73
 	cd_entry = str_hash_get(
 			hts->credit_data_by_client, call->client_id.s, call->client_id.len);
96596e28
 
db004a73
 	if(cd_entry == NULL) {
 		LM_ERR("Credit data not found for CID [%.*s], client-ID [%.*s]\n",
 				callid->len, callid->s, call->client_id.len, call->client_id.s);
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 		return;
 	}
 
db004a73
 	credit_data = (credit_data_t *)cd_entry->u.p;
96596e28
 
db004a73
 	if(credit_data == NULL) {
516eee4a
 		LM_ERR("[%.*s]: credit_data pointer is null\n", callid->len, callid->s);
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 		return;
 	}
 
a4cdd61a
 	cnxcc_unlock(hts->lock);
c587ce0b
 
96596e28
 	/*
 	 * Update calls statistics
 	 */
a4cdd61a
 	cnxcc_lock(_data.lock);
96596e28
 
 	_data.stats->active--;
 	_data.stats->total--;
 
a4cdd61a
 	cnxcc_unlock(_data.lock);
96596e28
 
a4cdd61a
 	cnxcc_lock(credit_data->lock);
96596e28
 
db004a73
 	LM_DBG("Call [%.*s] of client-ID [%.*s], ended\n", callid->len, callid->s,
 			call->client_id.len, call->client_id.s);
96596e28
 
 	/*
 	 * This call just ended and we need to remove it from the summ.
 	 */
db004a73
 	if(call->confirmed) {
96596e28
 		credit_data->concurrent_calls--;
 		credit_data->ended_calls_consumed_amount += call->consumed_amount;
c587ce0b
 
db004a73
 		if(_data.redis) {
c587ce0b
 			redis_incr_by_int(credit_data, "concurrent_calls", -1);
db004a73
 			redis_incr_by_double(credit_data, "ended_calls_consumed_amount",
 					call->consumed_amount);
c587ce0b
 		}
96596e28
 	}
 
 	credit_data->number_of_calls--;
 
db004a73
 	if(_data.redis)
c587ce0b
 		redis_incr_by_int(credit_data, "number_of_calls", -1);
 
db004a73
 	if(credit_data->concurrent_calls < 0) {
 		LM_ERR("[BUG]: number of concurrent calls dropped to negative value: "
 			   "%d\n",
 				credit_data->concurrent_calls);
96596e28
 	}
 
db004a73
 	if(credit_data->number_of_calls < 0) {
 		LM_ERR("[BUG]: number of calls dropped to negative value: %d\n",
 				credit_data->number_of_calls);
96596e28
 	}
 
 	/*
 	 * Remove (and free) the call from the list of calls of the current credit_data
 	 */
 	clist_rm(call, next, prev);
db004a73
 
d0bdaa75
 	/* return if credit_data is being deallocated.
 	 * the call and the credit data will be freed by terminate_all_calls()
a4cdd61a
 	 */
db004a73
 	if(credit_data->deallocating) {
d0bdaa75
 		return;
a4cdd61a
 	}
96596e28
 
d0bdaa75
 	__free_call(call);
96596e28
 	/*
 	 * In case there are no active calls for a certain client, we remove the client-id from the hash table.
 	 * This way, we can save memory for useful clients.
d0bdaa75
 	 *
96596e28
 	 */
db004a73
 	if(credit_data->number_of_calls == 0) {
 		LM_DBG("Removing client [%.*s] and its calls from the list\n",
 				credit_data->call_list->client_id.len,
 				credit_data->call_list->client_id.s);
96596e28
 
c587ce0b
 		credit_data->deallocating = 1;
a4cdd61a
 		cnxcc_lock(hts->lock);
c587ce0b
 
db004a73
 		if(_data.redis) {
c587ce0b
 			redis_clean_up_if_last(credit_data);
 			shm_free(credit_data->str_id);
 		}
 
96596e28
 		/*
 		 * Remove the credit_data_t from the hash table
 		 */
 		str_hash_del(cd_entry);
 
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 
 		/*
 		 * Free client_id in list's root
 		 */
 		shm_free(credit_data->call_list->client_id.s);
 		shm_free(credit_data->call_list);
 
 		/*
 		 * Release the lock since we are going to free the entry down below
 		 */
a4cdd61a
 		cnxcc_unlock(credit_data->lock);
96596e28
 
 		/*
 		 * Free the whole entry
 		 */
c587ce0b
 		__free_credit_data_hash_entry(cd_entry);
96596e28
 
 		/*
 		 * return without releasing the acquired lock over credit_data. Why? Because we just freed it.
 		 */
 		return;
 	}
 
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
96596e28
 }
 
db004a73
 static void __setup_billing(
 		str *callid, unsigned int h_entry, unsigned int h_id)
 {
 	call_t *call = NULL;
 	hash_tables_t *hts = NULL;
96596e28
 
db004a73
 	LM_DBG("Creating dialog for [%.*s], h_id [%u], h_entry [%u]\n", callid->len,
 			callid->s, h_id, h_entry);
96596e28
 
db004a73
 	//	cnxcc_lock(&_data);
96596e28
 
 	/*
 	 * Search call data by call-id
 	 */
db004a73
 	if(try_get_call_entry(callid, &call, &hts) != 0) {
516eee4a
 		LM_ERR("Call [%.*s] not found\n", callid->len, callid->s);
96596e28
 		return;
 	}
 
db004a73
 	if(call == NULL) {
516eee4a
 		LM_ERR("[%.*s] call pointer is null\n", callid->len, callid->s);
96596e28
 		return;
 	}
 
db004a73
 	if(hts == NULL) {
 		LM_ERR("[%.*s] result hashtable pointer is null\n", callid->len,
 				callid->s);
96596e28
 		return;
 	}
 
 	/*
 	 * Update calls statistics
 	 */
a4cdd61a
 	cnxcc_lock(_data.lock);
96596e28
 
 	_data.stats->active++;
 	_data.stats->total++;
 
a4cdd61a
 	cnxcc_unlock(_data.lock);
96596e28
 
a4cdd61a
 	cnxcc_lock(call->lock);
96596e28
 
db004a73
 	call->dlg_h_entry = h_entry;
 	call->dlg_h_id = h_id;
96596e28
 
db004a73
 	LM_DBG("Call [%.*s] from client [%.*s], created\n", callid->len, callid->s,
 			call->client_id.len, call->client_id.s);
96596e28
 
a4cdd61a
 	cnxcc_unlock(call->lock);
96596e28
 }
 
db004a73
 static void __start_billing(
 		str *callid, str *from_uri, str *to_uri, str tags[2])
 {
 	struct str_hash_entry *cd_entry = NULL;
 	call_t *call = NULL;
 	hash_tables_t *hts = NULL;
 	credit_data_t *credit_data = NULL;
96596e28
 
516eee4a
 	LM_DBG("Billing started for call [%.*s]\n", callid->len, callid->s);
96596e28
 
db004a73
 	//	cnxcc_lock(&_data);
96596e28
 
 	/*
 	 * Search call data by call-id
 	 */
db004a73
 	if(try_get_call_entry(callid, &call, &hts) != 0) {
516eee4a
 		LM_ERR("Call [%.*s] not found\n", callid->len, callid->s);
96596e28
 		return;
 	}
 
db004a73
 	if(call == NULL) {
516eee4a
 		LM_ERR("[%.*s] call pointer is null\n", callid->len, callid->s);
96596e28
 		return;
 	}
 
db004a73
 	if(hts == NULL) {
 		LM_ERR("[%.*s] result hashtable pointer is null", callid->len,
 				callid->s);
96596e28
 		return;
 	}
 
a4cdd61a
 	cnxcc_lock(hts->lock);
96596e28
 
 	/*
 	 * Search credit_data by client_id
 	 */
db004a73
 	cd_entry = str_hash_get(
 			hts->credit_data_by_client, call->client_id.s, call->client_id.len);
96596e28
 
db004a73
 	if(cd_entry == NULL) {
 		LM_ERR("Credit data not found for CID [%.*s], client-ID [%.*s]\n",
 				callid->len, callid->s, call->client_id.len, call->client_id.s);
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 		return;
 	}
 
db004a73
 	credit_data = (credit_data_t *)cd_entry->u.p;
96596e28
 
db004a73
 	if(credit_data == NULL) {
516eee4a
 		LM_ERR("[%.*s]: credit_data pointer is null\n", callid->len, callid->s);
a4cdd61a
 		cnxcc_unlock(hts->lock);
96596e28
 		return;
 	}
 
a4cdd61a
 	cnxcc_unlock(hts->lock);
96596e28
 
a4cdd61a
 	cnxcc_lock(credit_data->lock);
96596e28
 
 	/*
 	 * Now that the call is confirmed, we can increase the count of "concurrent_calls".
 	 * This will impact in the discount rate performed by the check_calls() function.
 	 *
 	 */
 	credit_data->concurrent_calls++;
 
db004a73
 	if(_data.redis)
c587ce0b
 		redis_incr_by_int(credit_data, "concurrent_calls", 1);
 
db004a73
 	if(credit_data->max_amount == 0) {
 		credit_data->max_amount = call->max_amount; // first time setup
96596e28
 
db004a73
 		if(_data.redis)
 			redis_insert_double_value(
 					credit_data, "max_amount", credit_data->max_amount);
c587ce0b
 	}
 
db004a73
 	if(call->max_amount > credit_data->max_amount) {
 		LM_ALERT("Maximum-talk-time/credit changed, maybe a credit reload? %f "
 				 "> %f. Client [%.*s]\n",
 				call->max_amount, credit_data->max_amount, call->client_id.len,
 				call->client_id.s);
96596e28
 
c587ce0b
 
db004a73
 		if(_data.redis)
 			redis_insert_double_value(credit_data, "max_amount",
 					call->max_amount - credit_data->max_amount);
c587ce0b
 
96596e28
 		credit_data->max_amount += call->max_amount - credit_data->max_amount;
 	}
 
 	/*
 	 * Update max_amount, discounting what was already consumed by other calls of the same client
 	 */
 	call->max_amount = credit_data->max_amount - credit_data->consumed_amount;
 
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
96596e28
 
a4cdd61a
 	cnxcc_lock(call->lock);
96596e28
 
 	/*
 	 * Store from-tag value
 	 */
db004a73
 	if(shm_str_dup(&call->sip_data.from_tag, &tags[0]) != 0) {
96596e28
 		LM_ERR("No more pkg memory\n");
 		goto exit;
 	}
 
 	/*
 	 * Store to-tag value
 	 */
db004a73
 	if(shm_str_dup(&call->sip_data.to_tag, &tags[1]) != 0) {
96596e28
 		LM_ERR("No more pkg memory\n");
 		goto exit;
 	}
 
db004a73
 	if(shm_str_dup(&call->sip_data.from_uri, from_uri) != 0
 			|| shm_str_dup(&call->sip_data.to_uri, to_uri) != 0) {
e5519a27
 		LM_ERR("No more pkg memory\n");
 		goto exit;
 	}
 
db004a73
 	call->start_timestamp = get_current_timestamp();
 	call->confirmed = TRUE;
96596e28
 
db004a73
 	LM_DBG("Call [%.*s] from client [%.*s], confirmed. from=<%.*s>;tag=%.*s, "
 		   "to=<%.*s>;tag=%.*s\n",
e5519a27
 			callid->len, callid->s, call->client_id.len, call->client_id.s,
 			call->sip_data.from_uri.len, call->sip_data.from_uri.s,
 			call->sip_data.from_tag.len, call->sip_data.from_tag.s,
 			call->sip_data.to_uri.len, call->sip_data.to_uri.s,
 			call->sip_data.to_tag.len, call->sip_data.to_tag.s);
96596e28
 exit:
a4cdd61a
 	cnxcc_unlock(call->lock);
96596e28
 }
 
c587ce0b
 // must be called with lock held on credit_data
d0bdaa75
 /* terminate all calls and remove credit_data */
db004a73
 void terminate_all_calls(credit_data_t *credit_data)
 {
 	call_t *call = NULL, *tmp = NULL;
d0bdaa75
 	struct str_hash_entry *cd_entry = NULL;
db004a73
 	hash_tables_t *hts = NULL;
d0bdaa75
 
 	switch(credit_data->type) {
db004a73
 		case CREDIT_MONEY:
 			hts = &_data.money;
 			break;
 		case CREDIT_TIME:
 			hts = &_data.time;
 			break;
 		case CREDIT_CHANNEL:
 			hts = &_data.channel;
 			break;
 		default:
 			LM_ERR("BUG: Something went terribly wrong\n");
 			return;
 	}
96596e28
 
5a577ab3
 	cd_entry = str_hash_get(hts->credit_data_by_client,
 			credit_data->call_list->client_id.s,
 			credit_data->call_list->client_id.len);
 
db004a73
 	if(cd_entry == NULL) {
5a577ab3
 		LM_WARN("credit data itme not found\n");
 		return;
 	}
c587ce0b
 	credit_data->deallocating = 1;
96596e28
 
db004a73
 	clist_foreach_safe(credit_data->call_list, call, tmp, next)
 	{
 		if(call->sip_data.callid.s != NULL) {
5a577ab3
 			LM_DBG("Killing call with CID [%.*s]\n", call->sip_data.callid.len,
 					call->sip_data.callid.s);
6985a295
 
 			/*
 			 * Update number of calls forced to end
 			 */
 			_data.stats->dropped++;
 			terminate_call(call);
 			__free_call(call);
 		} else {
 			LM_WARN("invalid call structure %p\n", call);
 		}
96596e28
 	}
d0bdaa75
 
 	cnxcc_lock(hts->lock);
 
db004a73
 	if(_data.redis) {
d0bdaa75
 		redis_clean_up_if_last(credit_data);
 		shm_free(credit_data->str_id);
 	}
5a577ab3
 
d0bdaa75
 	/*
      * Remove the credit_data_t from the hash table
 	 */
 	str_hash_del(cd_entry);
5a577ab3
 
d0bdaa75
 	cnxcc_unlock(hts->lock);
5a577ab3
 
d0bdaa75
 	/*
 	 * Free client_id in list's root
 	 */
 	shm_free(credit_data->call_list->client_id.s);
 	shm_free(credit_data->call_list);
5a577ab3
 
d0bdaa75
 	/*
 	 * Release the lock since we are going to free the entry down below
 	 */
 	cnxcc_unlock(credit_data->lock);
5a577ab3
 
d0bdaa75
 	/*
 	 * Free the whole entry
 	 */
 	__free_credit_data_hash_entry(cd_entry);
96596e28
 }
 
 /*
  * WARNING: When calling this function, the proper lock should have been acquired
  */
db004a73
 static void __free_call(call_t *call)
 {
c587ce0b
 	struct str_hash_entry *e = NULL;
96596e28
 
db004a73
 	if(call->sip_data.callid.s == NULL)
ce5591e8
 		return;
 
db004a73
 	LM_DBG("Freeing call [%.*s]\n", call->sip_data.callid.len,
 			call->sip_data.callid.s);
 	e = str_hash_get(_data.money.call_data_by_cid, call->sip_data.callid.s,
 			call->sip_data.callid.len);
96596e28
 
db004a73
 	if(e == NULL) {
 		e = str_hash_get(_data.time.call_data_by_cid, call->sip_data.callid.s,
 				call->sip_data.callid.len);
96596e28
 
db004a73
 		if(e == NULL) {
 			e = str_hash_get(_data.channel.call_data_by_cid,
 					call->sip_data.callid.s, call->sip_data.callid.len);
fc83d3b1
 
db004a73
 			if(e == NULL) {
 				LM_ERR("Call [%.*s] not found. Couldn't be able to free it "
 					   "from hashtable",
 						call->sip_data.callid.len, call->sip_data.callid.s);
fc83d3b1
 				return;
 			}
96596e28
 		}
 	}
 
 	str_hash_del(e);
 
 	shm_free(e->key.s);
 	shm_free(e);
 
 	str_shm_free_if_not_null(call->sip_data.callid);
c587ce0b
 	str_shm_free_if_not_null(call->sip_data.to_uri);
96596e28
 	str_shm_free_if_not_null(call->sip_data.to_tag);
c587ce0b
 	str_shm_free_if_not_null(call->sip_data.from_uri);
96596e28
 	str_shm_free_if_not_null(call->sip_data.from_tag);
 
 	shm_free(call);
 }
 
 /*
  * WARNING: When calling this function, the proper lock should have been acquired
  */
db004a73
 static void __free_credit_data_hash_entry(struct str_hash_entry *e)
 {
96596e28
 	shm_free(e->key.s);
db004a73
 	//	shm_free(((credit_data_t *) e->u.p)->call);
96596e28
 	shm_free(e->u.p);
 	shm_free(e);
 }
 
db004a73
 static int __shm_str_hash_alloc(struct str_hash_table *ht, int size)
 {
 	ht->table = shm_malloc(sizeof(struct str_hash_head) * size);
96596e28
 
db004a73
 	if(!ht->table)
96596e28
 		return -1;
 
db004a73
 	ht->size = size;
96596e28
 	return 0;
 }
 
db004a73
 int terminate_call(call_t *call)
 {
6177e766
 	sip_msg_t *dmsg = NULL;
 	sip_data_t *data = NULL;
 
dcb82e22
 	dlg_cell_t *cell;
 
db004a73
 	LM_DBG("Got kill signal for call [%.*s] client [%.*s] h_id [%u] h_entry "
 		   "[%u]. Dropping it now\n",
 			call->sip_data.callid.len, call->sip_data.callid.s,
 			call->client_id.len, call->client_id.s, call->dlg_h_id,
 			call->dlg_h_entry);
 
 	data = &call->sip_data;
 	if(faked_msg_init_with_dlg_info(&data->callid, &data->from_uri,
 			   &data->from_tag, &data->to_uri, &data->to_tag, &dmsg)
 			!= 0) {
 		LM_ERR("[%.*s]: error generating faked sip message\n", data->callid.len,
 				data->callid.s);
c587ce0b
 		goto error;
 	}
 
dcb82e22
 	cell = _dlgbinds.get_dlg(dmsg);
db004a73
 	if(!cell) {
 		LM_ERR("[%.*s]: cannot get dialog\n", data->callid.len, data->callid.s);
 		goto error;
c587ce0b
 	}
 
db004a73
 	if(!_dlgbinds.terminate_dlg(cell, NULL)) {
 		LM_DBG("dlg_end_dlg sent to call [%.*s]\n", call->sip_data.callid.len,
 				call->sip_data.callid.s);
c587ce0b
 
db004a73
 		if(_data.cs_route_number >= 0)
dcb82e22
 			__notify_call_termination(dmsg);
c587ce0b
 		return 0;
 	}
 
dcb82e22
 	LM_ERR("Error executing terminate_dlg command");
c587ce0b
 error:
 	return -1;
 }
 
db004a73
 static credit_data_t *__get_or_create_credit_data_entry(
 		str *client_id, credit_type_t type)
 {
a4cdd61a
 	struct str_hash_table *sht = NULL;
 	struct hash_tables *ht;
c587ce0b
 	struct str_hash_entry *e = NULL;
 	credit_data_t *credit_data = NULL;
 
 	switch(type) {
db004a73
 		case CREDIT_MONEY:
 			sht = _data.money.credit_data_by_client;
 			ht = &_data.money;
 			break;
 		case CREDIT_TIME:
 			sht = _data.time.credit_data_by_client;
 			ht = &_data.time;
 			break;
 		case CREDIT_CHANNEL:
 			sht = _data.channel.credit_data_by_client;
 			ht = &_data.channel;
 			break;
 		default:
 			LM_ERR("BUG: Something went terribly wrong\n");
 			return NULL;
fc83d3b1
 	}
 
a4cdd61a
 	cnxcc_lock(ht->lock);
 	e = str_hash_get(sht, client_id->s, client_id->len);
 	cnxcc_unlock(ht->lock);
96596e28
 
 	/*
 	 * Alloc new call_array_t if it doesn't exist
 	 */
db004a73
 	if(e != NULL)
96596e28
 		LM_DBG("Found key %.*s in hash table\n", e->key.len, e->key.s);
db004a73
 	else if(e == NULL) {
c587ce0b
 		e = shm_malloc(sizeof(struct str_hash_entry));
db004a73
 		if(e == NULL)
c587ce0b
 			goto no_memory;
96596e28
 
db004a73
 		if(shm_str_dup(&e->key, client_id) != 0)
c587ce0b
 			goto no_memory;
96596e28
 
c587ce0b
 		e->u.p = credit_data = __alloc_new_credit_data(client_id, type);
 		e->flags = 0;
96596e28
 
db004a73
 		if(credit_data == NULL)
c587ce0b
 			goto no_memory;
96596e28
 
a4cdd61a
 		cnxcc_lock(ht->lock);
 		str_hash_add(sht, e);
 		cnxcc_unlock(ht->lock);
96596e28
 
a4cdd61a
 		LM_DBG("Credit entry didn't exist. Allocated new entry [%p]\n", e);
96596e28
 	}
 
db004a73
 	return (credit_data_t *)e->u.p;
c587ce0b
 
 no_memory:
 	LM_ERR("No shared memory left\n");
 	return NULL;
96596e28
 }
 
db004a73
 static credit_data_t *__alloc_new_credit_data(
 		str *client_id, credit_type_t type)
 {
 	credit_data_t *credit_data = shm_malloc(sizeof(credit_data_t));
 	;
96596e28
 
db004a73
 	if(credit_data == NULL)
d0bdaa75
 		goto no_memory;
db004a73
 
d0bdaa75
 	memset(credit_data, 0, sizeof(credit_data_t));
 
a4cdd61a
 	cnxcc_lock_init(credit_data->lock);
96596e28
 
c587ce0b
 	credit_data->call_list = shm_malloc(sizeof(call_t));
96596e28
 
db004a73
 	if(credit_data->call_list == NULL)
c587ce0b
 		goto no_memory;
96596e28
 
c587ce0b
 	clist_init(credit_data->call_list, next, prev);
96596e28
 
c587ce0b
 	/*
 	 * Copy the client_id value to the root of the calls list.
 	 * This will be used later to get the credit_data_t of the
 	 * call when it is being searched by call ID.
 	 */
db004a73
 	if(shm_str_dup(&credit_data->call_list->client_id, client_id) != 0)
c587ce0b
 		goto no_memory;
96596e28
 
db004a73
 	if(_data.redis) {
c587ce0b
 		credit_data->str_id = shm_malloc(client_id->len + 1);
 
db004a73
 		if(!credit_data->str_id)
c587ce0b
 			goto no_memory;
 
 		memset(credit_data->str_id, 0, client_id->len + 1);
db004a73
 		snprintf(credit_data->str_id, client_id->len + 1, "%.*s",
 				client_id->len, client_id->s);
96596e28
 	}
 
db004a73
 	credit_data->max_amount = 0;
c587ce0b
 	credit_data->concurrent_calls = 0;
 	credit_data->consumed_amount = 0;
db004a73
 	credit_data->ended_calls_consumed_amount = 0;
c587ce0b
 	credit_data->number_of_calls = 0;
 	credit_data->type = type;
 	credit_data->deallocating = 0;
 
db004a73
 	if(!_data.redis)
c587ce0b
 		return credit_data;
 
db004a73
 	if(redis_get_or_create_credit_data(credit_data) < 0)
96596e28
 		goto error;
 
c587ce0b
 	return credit_data;
96596e28
 
c587ce0b
 no_memory:
db004a73
 	LM_ERR("No shared memory left\n");
96596e28
 error:
db004a73
 	return NULL;
96596e28
 }
 
db004a73
 static call_t *__alloc_new_call_by_money(credit_data_t *credit_data,
c64518a2
 		struct sip_msg *msg, double credit, double connect_cost,
 		double cost_per_second, int initial_pulse, int final_pulse)
db004a73
 {
c587ce0b
 	call_t *call = NULL;
a4cdd61a
 
 	cnxcc_lock(credit_data->lock);
96596e28
 
db004a73
 	if(credit_data->call_list == NULL) {
96596e28
 		LM_ERR("Credit data call list is NULL\n");
 		goto error;
 	}
 
c587ce0b
 	call = shm_malloc(sizeof(call_t));
db004a73
 	if(call == NULL) {
96596e28
 		LM_ERR("No shared memory left\n");
 		goto error;
 	}
 
db004a73
 	if((!msg->callid && parse_headers(msg, HDR_CALLID_F, 0) != 0)
 			|| shm_str_dup(&call->sip_data.callid, &msg->callid->body) != 0) {
96596e28
 		LM_ERR("Error processing CALLID hdr\n");
 		goto error;
 	}
 
db004a73
 	call->sip_data.to_uri.s = NULL;
 	call->sip_data.to_uri.len = 0;
 	call->sip_data.to_tag.s = NULL;
 	call->sip_data.to_tag.len = 0;
c587ce0b
 
db004a73
 	call->sip_data.from_uri.s = NULL;
 	call->sip_data.from_uri.len = 0;
 	call->sip_data.from_tag.s = NULL;
 	call->sip_data.from_tag.len = 0;
96596e28
 
c587ce0b
 	call->consumed_amount = initial_pulse * cost_per_second;
c64518a2
 	call->connect_amount = connect_cost;
db004a73
 	call->confirmed = FALSE;
c587ce0b
 	call->max_amount = credit;
96596e28
 
c64518a2
 	call->money_based.connect_cost = connect_cost;
db004a73
 	call->money_based.cost_per_second = cost_per_second;
 	call->money_based.initial_pulse = initial_pulse;
 	call->money_based.final_pulse = final_pulse;
96596e28
 
 	/*
 	 * Reference the client_id from the root of the list
 	 */
db004a73
 	call->client_id.s = credit_data->call_list->client_id.s;
 	call->client_id.len = credit_data->call_list->client_id.len;
96596e28
 
 	/*
 	 * Insert the newly created call to the list of calls
 	 */
 	clist_insert(credit_data->call_list, call, next, prev);
 
a4cdd61a
 	cnxcc_lock_init(call->lock);
96596e28
 
 	/*
 	 * Increase the number of calls for this client. This call is not yet confirmed.
 	 */
 	credit_data->number_of_calls++;
db004a73
 	if(_data.redis)
c587ce0b
 		redis_incr_by_int(credit_data, "number_of_calls", 1);
96596e28
 
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
96596e28
 
db004a73
 	LM_DBG("New call allocated for client [%.*s]\n", call->client_id.len,
 			call->client_id.s);
96596e28
 
 	return call;
 
 error:
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
96596e28
 	return NULL;
 }
 
db004a73
 static call_t *__alloc_new_call_by_time(
 		credit_data_t *credit_data, struct sip_msg *msg, int max_secs)
 {
c587ce0b
 	call_t *call = NULL;
96596e28
 
a4cdd61a
 	cnxcc_lock(credit_data->lock);
96596e28
 
db004a73
 	if(credit_data->call_list == NULL) {
96596e28
 		LM_ERR("Credit data call list is NULL\n");
 		goto error;
 	}
 
c587ce0b
 	call = shm_malloc(sizeof(call_t));
db004a73
 	if(call == NULL) {
96596e28
 		LM_ERR("No shared memory left\n");
 		goto error;
 	}
 
db004a73
 	if((!msg->callid && parse_headers(msg, HDR_CALLID_F, 0) != 0)
 			|| shm_str_dup(&call->sip_data.callid, &msg->callid->body) != 0) {
96596e28
 		LM_ERR("Error processing CALLID hdr\n");
 		goto error;
 	}
 
db004a73
 	call->sip_data.to_uri.s = NULL;
 	call->sip_data.to_uri.len = 0;
 	call->sip_data.to_tag.s = NULL;
 	call->sip_data.to_tag.len = 0;
96596e28
 
db004a73
 	call->sip_data.from_uri.s = NULL;
 	call->sip_data.from_uri.len = 0;
 	call->sip_data.from_tag.s = NULL;
 	call->sip_data.from_tag.len = 0;
 
 	call->consumed_amount = 0;
 	call->confirmed = FALSE;
 	call->max_amount = max_secs;
96596e28
 
 	/*
 	 * Reference the client_id from the root of the list
 	 */
db004a73
 	call->client_id.s = credit_data->call_list->client_id.s;
 	call->client_id.len = credit_data->call_list->client_id.len;
96596e28
 
 	/*
 	 * Insert the newly created call to the list of calls
 	 */
 	clist_insert(credit_data->call_list, call, next, prev);
 
a4cdd61a
 	cnxcc_lock_init(call->lock);
96596e28
 
 	/*
 	 * Increase the number of calls for this client. This call is not yet confirmed.
 	 */
 	credit_data->number_of_calls++;
db004a73
 	if(_data.redis)
c587ce0b
 		redis_incr_by_int(credit_data, "number_of_calls", 1);
96596e28
 
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
96596e28
 
db004a73
 	LM_DBG("New call allocated for client [%.*s]\n", call->client_id.len,
 			call->client_id.s);
96596e28
 
 	return call;
 
 error:
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
96596e28
 	return NULL;
 }
 
db004a73
 static call_t *alloc_new_call_by_channel(
 		credit_data_t *credit_data, struct sip_msg *msg, int max_chan)
 {
c587ce0b
 	call_t *call = NULL;
fc83d3b1
 
a4cdd61a
 	cnxcc_lock(credit_data->lock);
fc83d3b1
 
db004a73
 	if(credit_data->call_list == NULL) {
fc83d3b1
 		LM_ERR("Credit data call list is NULL\n");
 		goto error;
 	}
 
c587ce0b
 	call = shm_malloc(sizeof(call_t));
db004a73
 	if(call == NULL) {
fc83d3b1
 		LM_ERR("No shared memory left\n");
 		goto error;
 	}
 
db004a73
 	if((!msg->callid && parse_headers(msg, HDR_CALLID_F, 0) != 0)
 			|| shm_str_dup(&call->sip_data.callid, &msg->callid->body) != 0) {
fc83d3b1
 		LM_ERR("Error processing CALLID hdr\n");
 		goto error;
 	}
 
db004a73
 	call->sip_data.to_uri.s = NULL;
 	call->sip_data.to_uri.len = 0;
 	call->sip_data.to_tag.s = NULL;
 	call->sip_data.to_tag.len = 0;
c587ce0b
 
db004a73
 	call->sip_data.from_uri.s = NULL;
 	call->sip_data.from_uri.len = 0;
 	call->sip_data.from_tag.s = NULL;
 	call->sip_data.from_tag.len = 0;
fc83d3b1
 
db004a73
 	call->consumed_amount = 0;
 	call->confirmed = FALSE;
 	call->max_amount = max_chan;
fc83d3b1
 
 	/*
 	 * Reference the client_id from the root of the list
 	 */
db004a73
 	call->client_id.s = credit_data->call_list->client_id.s;
 	call->client_id.len = credit_data->call_list->client_id.len;
fc83d3b1
 
 	/*
 	 * Insert the newly created call to the list of calls
 	 */
 	clist_insert(credit_data->call_list, call, next, prev);
 
a4cdd61a
 	cnxcc_lock_init(call->lock);
fc83d3b1
 
 	/*
 	 * Increase the number of calls for this client. This call is not yet confirmed.
 	 */
 	credit_data->number_of_calls++;
db004a73
 	if(_data.redis)
c587ce0b
 		redis_incr_by_int(credit_data, "number_of_calls", 1);
fc83d3b1
 
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
fc83d3b1
 
db004a73
 	LM_DBG("New call allocated for client [%.*s]\n", call->client_id.len,
 			call->client_id.s);
fc83d3b1
 
 
 	return call;
 
 error:
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
fc83d3b1
 	return NULL;
 }
 
db004a73
 static int __add_call_by_cid(str *cid, call_t *call, credit_type_t type)
 {
 	struct str_hash_table *ht = NULL;
a4cdd61a
 	cnxcc_lock_t lock;
db004a73
 	struct str_hash_entry *e = NULL;
fc83d3b1
 
c587ce0b
 	switch(type) {
db004a73
 		case CREDIT_MONEY:
 			ht = _data.money.call_data_by_cid;
 			lock = _data.money.lock;
 			break;
 		case CREDIT_TIME:
 			ht = _data.time.call_data_by_cid;
 			lock = _data.time.lock;
 			break;
 		case CREDIT_CHANNEL:
 			ht = _data.channel.call_data_by_cid;
 			lock = _data.channel.lock;
 			break;
 		default:
 			LM_ERR("Something went terribly wrong\n");
 			return -1;
fc83d3b1
 	}
96596e28
 
c587ce0b
 	e = str_hash_get(ht, cid->s, cid->len);
96596e28
 
db004a73
 	if(e != NULL) {
96596e28
 		LM_DBG("e != NULL\n");
 
db004a73
 		call_t *value = (call_t *)e->u.p;
 		if(value == NULL) {
96596e28
 			LM_ERR("Value of CID [%.*s] is NULL\n", cid->len, cid->s);
 			return -1;
 		}
 
db004a73
 		LM_WARN("value cid: len=%d | value [%.*s]", value->sip_data.callid.len,
 				value->sip_data.callid.len, value->sip_data.callid.s);
c587ce0b
 		LM_WARN("added cid: len=%d | value [%.*s]", cid->len, cid->len, cid->s);
96596e28
 
db004a73
 		if(value->sip_data.callid.len != cid->len
 				|| strncasecmp(value->sip_data.callid.s, cid->s, cid->len)
 						   != 0) {
 			LM_ERR("Value of CID is [%.*s] and differs from value being added "
 				   "[%.*s]\n",
 					cid->len, cid->s, value->sip_data.callid.len,
 					value->sip_data.callid.s);
96596e28
 			return -1;
 		}
 
 		LM_DBG("CID already present\n");
 		return 0;
 	}
 
c587ce0b
 	e = shm_malloc(sizeof(struct str_hash_entry));
96596e28
 
db004a73
 	if(e == NULL) {
96596e28
 		LM_ERR("No shared memory left\n");
 		return -1;
 	}
 
db004a73
 	if(shm_str_dup(&e->key, cid) != 0) {
96596e28
 		LM_ERR("No shared memory left\n");
 		return -1;
 	}
 
db004a73
 	e->u.p = call;
96596e28
 
a4cdd61a
 	cnxcc_lock(lock);
96596e28
 	str_hash_add(ht, e);
a4cdd61a
 	cnxcc_unlock(lock);
96596e28
 
 	return 0;
 }
 
db004a73
 static inline void set_ctrl_flag(struct sip_msg *msg)
 {
 	if(_data.ctrl_flag != -1) {
96596e28
 		LM_DBG("Flag set!\n");
 		setflag(msg, _data.ctrl_flag);
 	}
 }
 
db004a73
 static inline int get_pv_value(
 		struct sip_msg *msg, pv_spec_t *spec, pv_value_t *value)
 {
 	if(pv_get_spec_value(msg, spec, value) != 0) {
96596e28
 		LM_ERR("Can't get PV's value\n");
 		return -1;
 	}
 
 	return 0;
 }
 
c64518a2
 static int ki_set_max_credit(sip_msg_t *msg, str *sclient, str *scredit,
 		str *sconnect, str *scps, int initp, int finishp)
db004a73
 {
 	credit_data_t *credit_data = NULL;
 	call_t *call = NULL;
96596e28
 
c64518a2
 	double credit = 0, connect_cost = 0, cost_per_second = 0;
96596e28
 
41953f19
 	if(msg->first_line.type != SIP_REQUEST
 			|| msg->first_line.u.request.method_value != METHOD_INVITE) {
 		LM_ERR("not supported - it has to be used for INVITE\n");
 		return -1;
 	}
96596e28
 
41953f19
 	if(__has_to_tag(msg)) {
 		LM_ERR("INVITE is a reINVITE\n");
 		return -1;
 	}
96596e28
 
b1899d28
 	if(sclient->len == 0 || sclient->s == NULL) {
41953f19
 		LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len,
 				msg->callid->body.s);
 		return -1;
 	}
96596e28
 
b1899d28
 	credit = str2double(scredit);
96596e28
 
41953f19
 	if(credit <= 0) {
 		LM_ERR("credit value must be > 0: %f", credit);
 		return -1;
 	}
96596e28
 
c64518a2
 	connect_cost = str2double(sconnect);
 
 	if(connect_cost < 0) {
 		LM_ERR("connect_cost value must be >= 0: %f\n", connect_cost);
 		return -1;
 	}
 
b1899d28
 	cost_per_second = str2double(scps);
96596e28
 
41953f19
 	if(cost_per_second <= 0) {
 		LM_ERR("cost_per_second value must be > 0: %f\n", cost_per_second);
 		return -1;
 	}
5ab17c02
 
41953f19
 	LM_DBG("Setting up new call for client [%.*s], max-credit[%f], "
c64518a2
 		   "connect-cost[%f], cost-per-sec[%f], initial-pulse [%d], "
 		   "final-pulse [%d], call-id[%.*s]\n",
 			sclient->len, sclient->s, credit, connect_cost, cost_per_second,
 			initp, finishp, msg->callid->body.len, msg->callid->body.s);
96596e28
 
41953f19
 	set_ctrl_flag(msg);
96596e28
 
b1899d28
 	if((credit_data = __get_or_create_credit_data_entry(sclient, CREDIT_MONEY))
41953f19
 			== NULL) {
 		LM_ERR("Error retrieving credit data from shared memory for client "
c64518a2
 			   "[%.*s]\n",
 				sclient->len, sclient->s);
41953f19
 		return -1;
 	}
96596e28
 
c64518a2
 	if((call = __alloc_new_call_by_money(credit_data, msg, credit, connect_cost,
41953f19
 				cost_per_second, initp, finishp))
 			== NULL) {
c64518a2
 		LM_ERR("Unable to allocate new call for client [%.*s]\n", sclient->len,
 				sclient->s);
41953f19
 		return -1;
 	}
96596e28
 
41953f19
 	if(__add_call_by_cid(&call->sip_data.callid, call, CREDIT_MONEY) != 0) {
 		LM_ERR("Unable to allocate new cid_by_client for client [%.*s]\n",
b1899d28
 				sclient->len, sclient->s);
96596e28
 		return -1;
 	}
 
 	return 1;
 }
 
c64518a2
 static int __set_max_credit(sip_msg_t *msg, char *pclient, char *pcredit,
 		char *pconnect, char *pcps, char *pinitp, char *pfinishp)
db004a73
 {
41953f19
 	str sclient;
b1899d28
 	str scredit;
c64518a2
 	str sconnect;
b1899d28
 	str scps;
 	int initp;
 	int finishp;
 
c64518a2
 	if(msg == NULL || pclient == NULL || pcredit == NULL || pconnect == NULL
 			|| pcps == NULL || pinitp == NULL || pfinishp == NULL) {
b1899d28
 		LM_ERR("invalid parameters\n");
 		return -1;
 	}
fc83d3b1
 
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pclient, &sclient) < 0) {
41953f19
 		LM_ERR("failed to get client parameter\n");
fc83d3b1
 		return -1;
 	}
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pcredit, &scredit) < 0) {
b1899d28
 		LM_ERR("failed to get credit parameter\n");
 		return -1;
 	}
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pconnect, &sconnect) < 0) {
 		LM_ERR("failed to get connect parameter\n");
 		return -1;
 	}
 	if(fixup_get_svalue(msg, (gparam_t *)pcps, &scps) < 0) {
b1899d28
 		LM_ERR("failed to get cps parameter\n");
 		return -1;
 	}
c64518a2
 	if(fixup_get_ivalue(msg, (gparam_t *)pinitp, &initp) < 0) {
b1899d28
 		LM_ERR("failed to get init pulse parameter\n");
 		return -1;
 	}
c64518a2
 	if(fixup_get_ivalue(msg, (gparam_t *)pfinishp, &finishp) < 0) {
b1899d28
 		LM_ERR("failed to get finish pulse parameter\n");
 		return -1;
 	}
fc83d3b1
 
c64518a2
 	return ki_set_max_credit(
 			msg, &sclient, &scredit, &sconnect, &scps, initp, finishp);
b1899d28
 }
 
 static int ki_terminate_all(sip_msg_t *msg, str *sclient)
 {
 	credit_data_t *credit_data = NULL;
 
 	if(sclient->len == 0 || sclient->s == NULL) {
db004a73
 		LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len,
 				msg->callid->body.s);
fc83d3b1
 		return -1;
 	}
 
b1899d28
 	if(try_get_credit_data_entry(sclient, &credit_data) != 0) {
c64518a2
 		LM_DBG("credit data for [%.*s] on [%.*s] not found\n", sclient->len,
 				sclient->s, msg->callid->body.len, msg->callid->body.s);
fc83d3b1
 		return -1;
 	}
 
 	terminate_all_calls(credit_data);
 	return 1;
 }
 
b1899d28
 static int __terminate_all(sip_msg_t *msg, char *pclient, char *p2)
db004a73
 {
b1899d28
 	str sclient;
 
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pclient, &sclient) < 0) {
b1899d28
 		LM_ERR("failed to get client parameter\n");
 		return -1;
 	}
 
 	return ki_terminate_all(msg, &sclient);
 }
fc83d3b1
 
c64518a2
 static int __get_channel_count_helper(
 		sip_msg_t *msg, str *sclient, pv_spec_t *pvcount)
b1899d28
 {
 	credit_data_t *credit_data = NULL;
41953f19
 	pv_value_t countval;
db004a73
 	int value = -1;
fc83d3b1
 
41953f19
 	if(!pv_is_w(pvcount)) {
 		LM_ERR("pvar is not writable\n");
 		return -1;
 	}
 
b1899d28
 	if(sclient->len == 0 || sclient->s == NULL) {
db004a73
 		LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len,
 				msg->callid->body.s);
fc83d3b1
 		return -1;
 	}
 
b1899d28
 	if(try_get_credit_data_entry(sclient, &credit_data) == 0)
db004a73
 		value = credit_data->number_of_calls;
fc83d3b1
 	else
b1899d28
 		LM_ALERT("[%.*s] [%.*s] not found\n", sclient->len, sclient->s,
 				msg->callid->body.len, msg->callid->body.s);
fc83d3b1
 
41953f19
 	memset(&countval, 0, sizeof(countval));
fc83d3b1
 
41953f19
 	countval.flags = PV_VAL_STR;
fc83d3b1
 
41953f19
 	countval.rs.s = sint2str(value, &countval.rs.len);
fc83d3b1
 
41953f19
 	if(pv_set_spec_value(msg, pvcount, 0, &countval) != 0) {
 		LM_ERR("Error writing value to pseudo-variable\n");
fc83d3b1
 		return -1;
 	}
 
 	return 1;
 }
 
b1899d28
 static int __get_channel_count(sip_msg_t *msg, char *pclient, char *pcount)
 {
 	str sclient;
 
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pclient, &sclient) < 0) {
b1899d28
 		LM_ERR("failed to get client parameter\n");
 		return -1;
 	}
 
 	return __get_channel_count_helper(msg, &sclient, (pv_spec_t *)pcount);
 }
 
 static int ki_get_channel_count(sip_msg_t *msg, str *sclient, str *pvname)
 {
 	pv_spec_t *pvcount = NULL;
 
 	pvcount = pv_cache_get(pvname);
 
c64518a2
 	if(pvcount == NULL) {
b1899d28
 		LM_ERR("failed to get pv spec for [%.*s]\n", pvname->len, pvname->s);
 		return -1;
 	}
 	return __get_channel_count_helper(msg, sclient, pvcount);
 }
 
 static int ki_set_max_channels(sip_msg_t *msg, str *sclient, int max_chan)
db004a73
 {
 	credit_data_t *credit_data = NULL;
 	call_t *call = NULL;
fc83d3b1
 
db004a73
 	if(parse_headers(msg, HDR_CALLID_F, 0) != 0) {
fc83d3b1
 		LM_ERR("Error parsing Call-ID");
 		return -1;
 	}
 
41953f19
 	if(msg->first_line.type != SIP_REQUEST
 			|| msg->first_line.u.request.method_value != METHOD_INVITE) {
 		LM_ALERT("MSG was not an INVITE\n");
 		return -1;
 	}
 	if(__has_to_tag(msg)) {
 		LM_ERR("INVITE is a reINVITE\n");
 		return -1;
 	}
fc83d3b1
 
41953f19
 	set_ctrl_flag(msg);
fc83d3b1
 
41953f19
 	if(max_chan <= 0) {
 		LM_ERR("[%.*s] MAX_CHAN cannot be less than or equal to zero: %d\n",
 				msg->callid->body.len, msg->callid->body.s, max_chan);
 		return -1;
 	}
fc83d3b1
 
b1899d28
 	if(sclient->len == 0 || sclient->s == NULL) {
41953f19
 		LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len,
 				msg->callid->body.s);
 		return -1;
 	}
db004a73
 
41953f19
 	LM_DBG("Setting up new call for client [%.*s], max-chan[%d], "
c64518a2
 		   "call-id[%.*s]\n",
 			sclient->len, sclient->s, max_chan, msg->callid->body.len,
 			msg->callid->body.s);
fc83d3b1
 
c64518a2
 	if((credit_data = __get_or_create_credit_data_entry(
 				sclient, CREDIT_CHANNEL))
 			== NULL) {
41953f19
 		LM_ERR("Error retrieving credit data from shared memory for client "
c64518a2
 			   "[%.*s]\n",
 				sclient->len, sclient->s);
41953f19
 		return -1;
 	}
fc83d3b1
 
41953f19
 	if(credit_data->number_of_calls + 1 > max_chan)
 		return -2; // you have, between calls being setup plus those established, more than you maximum quota
fc83d3b1
 
41953f19
 	if(credit_data->concurrent_calls + 1 > max_chan)
 		return -3; // you have the max amount of established calls already
fc83d3b1
 
c64518a2
 	if((call = alloc_new_call_by_channel(credit_data, msg, max_chan)) == NULL) {
 		LM_ERR("Unable to allocate new call for client [%.*s]\n", sclient->len,
 				sclient->s);
41953f19
 		return -1;
 	}
fc83d3b1
 
c64518a2
 	if(__add_call_by_cid(&call->sip_data.callid, call, CREDIT_CHANNEL) != 0) {
41953f19
 		LM_ERR("Unable to allocate new cid_by_client for client [%.*s]\n",
b1899d28
 				sclient->len, sclient->s);
fc83d3b1
 		return -1;
 	}
41953f19
 
 	return 1;
fc83d3b1
 }
 
b1899d28
 static int __set_max_channels(sip_msg_t *msg, char *pclient, char *pmaxchan)
db004a73
 {
41953f19
 	str sclient;
b1899d28
 	int max_chan = 0;
96596e28
 
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pclient, &sclient) < 0) {
b1899d28
 		LM_ERR("failed to get client parameter\n");
 		return -1;
 	}
c64518a2
 	if(fixup_get_ivalue(msg, (gparam_t *)pmaxchan, &max_chan) < 0) {
b1899d28
 		LM_ERR("failed to get max chan parameter\n");
 		return -1;
 	}
 
 	return ki_set_max_channels(msg, &sclient, max_chan);
 }
 
 static int ki_set_max_time(sip_msg_t *msg, str *sclient, int max_secs)
 {
 	credit_data_t *credit_data = NULL;
 	call_t *call = NULL;
96596e28
 
db004a73
 	if(parse_headers(msg, HDR_CALLID_F, 0) != 0) {
c587ce0b
 		LM_ERR("Error parsing Call-ID");
96596e28
 		return -1;
 	}
 
41953f19
 	if(msg->first_line.type != SIP_REQUEST
 			|| msg->first_line.u.request.method_value != METHOD_INVITE) {
 		LM_ALERT("MSG was not an INVITE\n");
 		return -1;
 	}
96596e28
 
41953f19
 	if(__has_to_tag(msg)) {
 		LM_ERR("INVITE is a reINVITE\n");
 		return -1;
 	}
96596e28
 
b1899d28
 	set_ctrl_flag(msg);
96596e28
 
41953f19
 	if(max_secs <= 0) {
 		LM_ERR("[%.*s] MAXSECS cannot be less than or equal to zero: %d\n",
 				msg->callid->body.len, msg->callid->body.s, max_secs);
 		return -1;
 	}
96596e28
 
b1899d28
 	if(sclient->len <= 0 || sclient->s == NULL) {
41953f19
 		LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len,
 				msg->callid->body.s);
 		return -1;
 	}
96596e28
 
41953f19
 	LM_DBG("Setting up new call for client [%.*s], max-secs[%d], "
c64518a2
 		   "call-id[%.*s]\n",
 			sclient->len, sclient->s, max_secs, msg->callid->body.len,
 			msg->callid->body.s);
db004a73
 
b1899d28
 	if((credit_data = __get_or_create_credit_data_entry(sclient, CREDIT_TIME))
41953f19
 			== NULL) {
 		LM_ERR("Error retrieving credit data from shared memory for client "
c64518a2
 			   "[%.*s]\n",
 				sclient->len, sclient->s);
41953f19
 		return -1;
 	}
96596e28
 
c64518a2
 	if((call = __alloc_new_call_by_time(credit_data, msg, max_secs)) == NULL) {
 		LM_ERR("Unable to allocate new call for client [%.*s]\n", sclient->len,
 				sclient->s);
41953f19
 		return -1;
 	}
96596e28
 
41953f19
 	if(__add_call_by_cid(&call->sip_data.callid, call, CREDIT_TIME) != 0) {
 		LM_ERR("Unable to allocate new cid_by_client for client [%.*s]\n",
b1899d28
 				sclient->len, sclient->s);
96596e28
 		return -1;
 	}
 
 	return 1;
 }
 
b1899d28
 static int __set_max_time(sip_msg_t *msg, char *pclient, char *pmaxsecs)
db004a73
 {
41953f19
 	str sclient;
b1899d28
 	int max_secs = 0;
0efe5a4d
 
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pclient, &sclient) < 0) {
41953f19
 		LM_ERR("failed to get client parameter\n");
 		return -1;
 	}
c64518a2
 	if(fixup_get_ivalue(msg, (gparam_t *)pmaxsecs, &max_secs) < 0) {
b1899d28
 		LM_ERR("failed to get max secs parameter\n");
 		return -1;
 	}
 
 	return ki_set_max_time(msg, &sclient, max_secs);
 }
 
 static int ki_update_max_time(sip_msg_t *msg, str *sclient, int secs)
 {
 	credit_data_t *credit_data = NULL;
 
 	set_ctrl_flag(msg);
 
 	if(parse_headers(msg, HDR_CALLID_F, 0) != 0) {
 		LM_ERR("Error parsing Call-ID");
0efe5a4d
 		return -1;
 	}
 
db004a73
 	if(secs <= 0) {
 		LM_ERR("[%.*s] MAXSECS cannot be less than or equal to zero: %d\n",
 				msg->callid->body.len, msg->callid->body.s, secs);
0efe5a4d
 		return -1;
 	}
 
b1899d28
 	if(sclient->len == 0 || sclient->s == NULL) {
db004a73
 		LM_ERR("[%.*s]: client ID cannot be null\n", msg->callid->body.len,
 				msg->callid->body.s);
0efe5a4d
 		return -1;
 	}
 
db004a73
 	LM_DBG("Updating call for client [%.*s], max-secs[%d], call-id[%.*s]\n",
c64518a2
 			sclient->len, sclient->s, secs, msg->callid->body.len,
 			msg->callid->body.s);
0efe5a4d
 
 
db004a73
 	struct str_hash_table *ht = NULL;
 	struct str_hash_entry *e = NULL;
 	ht = _data.time.credit_data_by_client;
 	double update_fraction = secs;
 	call_t *call = NULL, *tmp_call = NULL;
0efe5a4d
 
a4cdd61a
 	cnxcc_lock(_data.time.lock);
b1899d28
 	e = str_hash_get(ht, sclient->s, sclient->len);
a4cdd61a
 	cnxcc_unlock(_data.time.lock);
0efe5a4d
 
db004a73
 	if(e == NULL) {
c64518a2
 		LM_ERR("Client [%.*s] was not found\n", sclient->len, sclient->s);
0efe5a4d
 		return -1;
 	}
db004a73
 
 	credit_data = (credit_data_t *)e->u.p;
a4cdd61a
 	cnxcc_lock(credit_data->lock);
0efe5a4d
 
db004a73
 	LM_DBG("Updating max-secs for [%.*s] from [%f] to [%f]\n", e->key.len,
 			e->key.s, credit_data->max_amount, credit_data->max_amount + secs);
0efe5a4d
 
db004a73
 	credit_data->max_amount += secs;
0efe5a4d
 
db004a73
 	if(credit_data->number_of_calls > 0)
 		update_fraction = secs / credit_data->number_of_calls;
 
 	clist_foreach_safe(credit_data->call_list, call, tmp_call, next)
 	{
 		if(!call->confirmed)
0efe5a4d
 			continue;
db004a73
 
 		call->max_amount += update_fraction;
0efe5a4d
 	}
 
41953f19
 	//redit_data->consumed_amount = 0;
0efe5a4d
 
a4cdd61a
 	cnxcc_unlock(credit_data->lock);
0efe5a4d
 
 	return 1;
 }
 
b1899d28
 static int __update_max_time(sip_msg_t *msg, char *pclient, char *psecs)
 {
 	str sclient;
 	int secs = 0;
 
c64518a2
 	if(fixup_get_svalue(msg, (gparam_t *)pclient, &sclient) < 0) {
b1899d28
 		LM_ERR("failed to get client parameter\n");
 		return -1;
 	}
c64518a2
 	if(fixup_get_ivalue(msg, (gparam_t *)psecs, &secs) < 0) {
b1899d28
 		LM_ERR("failed to get secs parameter\n");
 		return -1;
 	}
 
 	return ki_update_max_time(msg, &sclient, secs);
 }
 
db004a73
 static int __has_to_tag(struct sip_msg *msg)
 {
 	if(msg->to == NULL && parse_headers(msg, HDR_TO_F, 0) != 0) {
96596e28
 		LM_ERR("Cannot parse to-tag\n");
 		return 0;
 	}
 
db004a73
 	return !(get_to(msg)->tag_value.s == NULL
 			 || get_to(msg)->tag_value.len == 0);
96596e28
 }
 
db004a73
 static int __pv_parse_calls_param(pv_spec_p sp, str *in)
 {
 	if(sp == NULL || in == NULL || in->len == 0)
96596e28
 		return -1;
 
c587ce0b
 	switch(in->len) {
db004a73
 		case 5:
 			if(strncmp("total", in->s, in->len) == 0)
 				sp->pvp.pvn.u.isname.name.n = CNX_PV_TOTAL;
 			else
 				return -1;
 			break;
 		case 6:
 			if(strncmp("active", in->s, in->len) == 0)
 				sp->pvp.pvn.u.isname.name.n = CNX_PV_ACTIVE;
 			else
 				return -1;
 			break;
 		case 7:
 			if(strncmp("dropped", in->s, in->len) == 0)
 				sp->pvp.pvn.u.isname.name.n = CNX_PV_DROPPED;
 			else
 				return -1;
 			break;
96596e28
 	}
 
 	sp->pvp.pvn.type = PV_NAME_INTSTR;
 	sp->pvp.pvn.u.isname.type = 0;
 
 	return 0;
 }
 
db004a73
 static int __pv_get_calls(
 		struct sip_msg *msg, pv_param_t *param, pv_value_t *res)
 {
c587ce0b
 	switch(param->pvn.u.isname.name.n) {
db004a73
 		case CNX_PV_ACTIVE:
 			return pv_get_uintval(msg, param, res, _data.stats->active);
 		case CNX_PV_TOTAL:
 			return pv_get_uintval(msg, param, res, _data.stats->total);
 		case CNX_PV_DROPPED:
 			return pv_get_uintval(msg, param, res, _data.stats->dropped);
 		default:
 			LM_ERR("Unknown PV type %d\n", param->pvn.u.isname.name.n);
 			break;
96596e28
 	}
 
 	return -1;
 }
 
db004a73
 void rpc_credit_control_stats(rpc_t *rpc, void *ctx)
d2e3b7c8
 {
 	void *rh;
96596e28
 
db004a73
 	if(rpc->add(ctx, "{", &rh) < 0) {
d2e3b7c8
 		rpc->fault(ctx, 500, "Server failure");
 		return;
96596e28
 	}
 
db004a73
 	rpc->struct_add(rh, "sddd", "info", "CNX Credit Control", "active",
 			_data.stats->active, "dropped", _data.stats->dropped, "total",
 			_data.stats->total);
b1899d28
 }
 
 /**
  *
  */
 /* clang-format off */
 static sr_kemi_t sr_kemi_cnxcc_exports[] = {
 	{ str_init("cnxcc"), str_init("set_max_credit"),
 		SR_KEMIP_INT, ki_set_max_credit,
 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_STR,
c47c759e
 			SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_INT }
b1899d28
 	},
 	{ str_init("cnxcc"), str_init("set_max_time"),
 		SR_KEMIP_INT, ki_set_max_time,
 		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
 	{ str_init("cnxcc"), str_init("update_max_time"),
 		SR_KEMIP_INT, ki_update_max_time,
 		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
 	{ str_init("cnxcc"), str_init("set_max_channels"),
 		SR_KEMIP_INT, ki_set_max_channels,
 		{ SR_KEMIP_STR, SR_KEMIP_INT, SR_KEMIP_NONE,
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
 	{ str_init("cnxcc"), str_init("get_channel_count"),
 		SR_KEMIP_INT, ki_get_channel_count,
c668854d
 		{ SR_KEMIP_STR, SR_KEMIP_STR, SR_KEMIP_NONE,
b1899d28
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
 	{ str_init("cnxcc"), str_init("terminate_all"),
 		SR_KEMIP_INT, ki_terminate_all,
 		{ SR_KEMIP_STR, SR_KEMIP_NONE, SR_KEMIP_NONE,
 			SR_KEMIP_NONE, SR_KEMIP_NONE, SR_KEMIP_NONE }
 	},
 
 	{ {0, 0}, {0, 0}, 0, NULL, { 0, 0, 0, 0, 0, 0 } }
 };
 /* clang-format on */
 
 int mod_register(char *path, int *dlflags, void *p1, void *p2)
 {
 	sr_kemi_modules_add(sr_kemi_cnxcc_exports);
 	return 0;
c668854d
 }