From 4e705b58ae51fa4c765af8a1f33e2ea50f348dec Mon Sep 17 00:00:00 2001 From: Stefan Sayer Date: Tue, 2 Nov 2010 12:41:37 +0100 Subject: [PATCH] sbc: added prepaid accounting --- apps/examples/tutorial/cc_acc/CCAcc.cpp | 5 + .../examples/tutorial/cc_acc_xmlrpc/CCAcc.cpp | 3 + apps/sbc/SBC.cpp | 202 ++++++++++++++++-- apps/sbc/SBC.h | 17 +- apps/sbc/etc/auth_b2b.sbcprofile.conf | 8 +- apps/sbc/etc/call_timer.sbcprofile.conf | 6 + apps/sbc/etc/sst_b2b.sbcprofile.conf | 6 + apps/sbc/etc/transparent.sbcprofile.conf | 8 +- doc/Readme.sbc.txt | 48 +++++ doc/Readme.sw_prepaid_sip.txt | 7 + 10 files changed, 284 insertions(+), 26 deletions(-) diff --git a/apps/examples/tutorial/cc_acc/CCAcc.cpp b/apps/examples/tutorial/cc_acc/CCAcc.cpp index bf1fb6bb..4ed6e1d6 100644 --- a/apps/examples/tutorial/cc_acc/CCAcc.cpp +++ b/apps/examples/tutorial/cc_acc/CCAcc.cpp @@ -39,6 +39,8 @@ CCAcc::~CCAcc() { } void CCAcc::invoke(const string& method, const AmArg& args, AmArg& ret) { + DBG("cc_acc: %s(%s)\n", method.c_str(), AmArg::print(args).c_str()); + if(method == "getCredit"){ assertArgCStr(args.get(0)); ret.push(getCredit(args.get(0).asCStr())); @@ -57,11 +59,14 @@ void CCAcc::invoke(const string& method, const AmArg& args, AmArg& ret) assertArgInt(args.get(1)); ret.push(setCredit(args.get(0).asCStr(), args.get(1).asInt())); + } else if(method == "connectCall"){ + // call is connected } else if(method == "_list"){ ret.push("getCredit"); ret.push("subtractCredit"); ret.push("setCredit"); ret.push("addCredit"); + ret.push("connectCall"); } else throw AmDynInvoke::NotImplemented(method); diff --git a/apps/examples/tutorial/cc_acc_xmlrpc/CCAcc.cpp b/apps/examples/tutorial/cc_acc_xmlrpc/CCAcc.cpp index 806f8510..6d05bd86 100644 --- a/apps/examples/tutorial/cc_acc_xmlrpc/CCAcc.cpp +++ b/apps/examples/tutorial/cc_acc_xmlrpc/CCAcc.cpp @@ -51,9 +51,12 @@ void CCAcc::invoke(const string& method, const AmArg& args, AmArg& ret) } else if(method == "subtractCredit"){ ret.push(subtractCredit(args.get(0).asCStr(), args.get(1).asInt())); + } else if(method == "connectCall"){ + // } else if(method == "_list"){ ret.push("getCredit"); ret.push("subtractCredit"); + ret.push("connectCall"); } else throw AmDynInvoke::NotImplemented(method); diff --git a/apps/sbc/SBC.cpp b/apps/sbc/SBC.cpp index bdaec105..588f2561 100644 --- a/apps/sbc/SBC.cpp +++ b/apps/sbc/SBC.cpp @@ -37,7 +37,7 @@ SBC - feature-wishlist - select profile on monitoring in-mem DB record - fallback profile - add headers - +- online profile reload */ #include "SBC.h" @@ -122,6 +122,20 @@ bool SBCCallProfile::readFromConfiguration(const string& name, call_timer_enabled = cfg.getParameter("enable_call_timer", "no") == "yes"; call_timer = cfg.getParameter("call_timer"); + prepaid_enabled = cfg.getParameter("enable_prepaid", "no") == "yes"; + prepaid_accmodule = cfg.getParameter("prepaid_accmodule"); + prepaid_uuid = cfg.getParameter("prepaid_uuid"); + prepaid_acc_dest = cfg.getParameter("prepaid_acc_dest"); + + // check for acc module if configured statically + if (prepaid_enabled && + (prepaid_accmodule.find('$') == string::npos) && + (NULL == AmPlugIn::instance()->getFactory4Di(prepaid_accmodule))) { + ERROR("prepaid accounting module '%s' used in call profile " + "'%s' is not loaded\n", prepaid_accmodule.c_str(), name.c_str()); + return false; + } + INFO("SBC: loaded SBC profile '%s':\n", name.c_str()); INFO("SBC: RURI = '%s'\n", ruri.c_str()); @@ -144,7 +158,13 @@ bool SBCCallProfile::readFromConfiguration(const string& name, if (call_timer_enabled) { INFO("SBC: %s seconds\n", call_timer.c_str()); } - + INFO("SBC: prepaid %sabled\n", prepaid_enabled?"en":"dis"); + if (prepaid_enabled) { + INFO("SBC: acc_module = '%s'\n", prepaid_accmodule.c_str()); + INFO("SBC: uuid = '%s'\n", prepaid_uuid.c_str()); + INFO("SBC: acc_dest = '%s'\n", prepaid_acc_dest.c_str()); + } + return true; } @@ -246,6 +266,7 @@ AmSession* SBCFactory::onInvite(const AmSipRequest& req) SBCDialog::SBCDialog(const SBCCallProfile& call_profile) // AmDynInvoke* user_timer) : m_state(BB_Init), + m_user_timer(NULL),prepaid_acc(NULL), call_profile(call_profile) { set_sip_relay_only(false); @@ -541,6 +562,21 @@ void SBCDialog::onInvite(const AmSipRequest& req) ruri_parser, from_parser, to_parser); } + // get timer + if (call_profile.call_timer_enabled || call_profile.prepaid_enabled) { + AmDynInvokeFactory* fact = + AmPlugIn::instance()->getFactory4Di("user_timer"); + if (NULL == fact) { + ERROR("load session_timer module for call timers\n"); + throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); + } + m_user_timer = fact->getInstance(); + if(NULL == m_user_timer) { + ERROR("could not get a timer reference\n"); + throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); + } + } + if (call_profile.call_timer_enabled) { call_profile.call_timer = replaceParameters("call_timer", call_profile.call_timer, req, app_param, @@ -554,18 +590,54 @@ void SBCDialog::onInvite(const AmSipRequest& req) // time=0 throw AmSession::Exception(503, "Service Unavailable"); } + } - AmDynInvokeFactory* fact = - AmPlugIn::instance()->getFactory4Di("user_timer"); - if (NULL == fact) { - ERROR("load session_timer module for call timers\n"); + if (call_profile.prepaid_enabled) { + call_profile.prepaid_accmodule = + replaceParameters("prepaid_accmodule", call_profile.prepaid_accmodule, + req, app_param, ruri_parser, from_parser, to_parser); + if (call_profile.prepaid_accmodule.empty()) { + ERROR("using prepaid but empty prepaid_accmodule!\n"); throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); } - m_user_timer = fact->getInstance(); - if(!m_user_timer) { - ERROR("could not get a timer reference\n"); + + AmDynInvokeFactory* pp_fact = + AmPlugIn::instance()->getFactory4Di(call_profile.prepaid_accmodule); + if (NULL == pp_fact) { + ERROR("prepaid_accmodule '%s' not loaded\n", call_profile.prepaid_accmodule.c_str()); throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); } + prepaid_acc = pp_fact->getInstance(); + if(NULL == prepaid_acc) { + ERROR("could not get a prepaid acc reference\n"); + throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); + } + + call_profile.prepaid_uuid = + replaceParameters("prepaid_uuid", call_profile.prepaid_uuid, + req, app_param, ruri_parser, from_parser, to_parser); + + call_profile.prepaid_acc_dest = + replaceParameters("prepaid_acc_dest", call_profile.prepaid_acc_dest, + req, app_param, ruri_parser, from_parser, to_parser); + + prepaid_starttime = time(NULL); + + AmArg di_args,ret; + di_args.push(call_profile.prepaid_uuid); + di_args.push(call_profile.prepaid_acc_dest); + di_args.push((int)prepaid_starttime); + di_args.push(getCallID()); + di_args.push(getLocalTag()); + prepaid_acc->invoke("getCredit", di_args, ret); + prepaid_credit = ret.get(0).asInt(); + if(prepaid_credit < 0) { + ERROR("Failed to fetch credit from accounting module\n"); + throw AmSession::Exception(500, SIP_REPLY_SERVER_INTERNAL_ERROR); + } + if (prepaid_credit == 0) { + throw AmSession::Exception(402,"Insufficient Credit"); + } } DBG("SBC: connecting to '%s'\n",ruri.c_str()); @@ -583,8 +655,14 @@ void SBCDialog::process(AmEvent* ev) if (timer_id == SBC_TIMER_ID_CALL_TIMER && getCalleeStatus() == Connected) { DBG("SBC: %us call timer hit - ending call\n", call_timer); - terminateOtherLeg(); - terminateLeg(); + stopCall(); + ev->processed = true; + return; + } else if (timer_id == SBC_TIMER_ID_PREPAID_TIMEOUT) { + DBG("timer timeout, no more credit\n"); + stopCall(); + ev->processed = true; + return; } } @@ -661,13 +739,16 @@ bool SBCDialog::onOtherReply(const AmSipReply& reply) else if(reply.code < 300) { if(getCalleeStatus() == Connected) { m_state = BB_Connected; + + if ((call_profile.call_timer_enabled || call_profile.prepaid_enabled) && + (NULL == m_user_timer)) { + ERROR("internal implementation error: invalid timer reference\n"); + terminateOtherLeg(); + terminateLeg(); + return ret; + } + if (call_profile.call_timer_enabled) { - if (NULL == m_user_timer) { - ERROR("internal implementation error: invalid timer reference\n"); - terminateOtherLeg(); - terminateLeg(); - return ret; - } DBG("SBC: starting call timer of %u seconds\n", call_timer); AmArg di_args,ret; di_args.push((int)SBC_TIMER_ID_CALL_TIMER); @@ -675,6 +756,8 @@ bool SBCDialog::onOtherReply(const AmSipReply& reply) di_args.push(getLocalTag().c_str()); m_user_timer->invoke("setTimer", di_args, ret); } + + startPrepaidAccounting(); } } else if(reply.code == 487 && dlg.getStatus() == AmSipDialog::Pending) { @@ -698,18 +781,14 @@ bool SBCDialog::onOtherReply(const AmSipReply& reply) void SBCDialog::onOtherBye(const AmSipRequest& req) { -// stopAccounting(); + stopPrepaidAccounting(); AmB2BCallerSession::onOtherBye(req); } void SBCDialog::onBye(const AmSipRequest& req) { - if (m_state == BB_Connected) { -// stopAccounting(); - } - terminateOtherLeg(); - setStopped(); + stopCall(); } @@ -724,6 +803,83 @@ void SBCDialog::onCancel() } } +void SBCDialog::stopCall() { + if (m_state == BB_Connected) { + stopPrepaidAccounting(); + } + terminateOtherLeg(); + terminateLeg(); +} + +void SBCDialog::startPrepaidAccounting() { + if (!call_profile.prepaid_enabled) + return; + + if (NULL == prepaid_acc) { + ERROR("Internal error, trying to use prepaid, but no prepaid_acc\n"); + terminateOtherLeg(); + terminateLeg(); + return; + } + + gettimeofday(&prepaid_acc_start, NULL); + + DBG("SBC: starting prepaid timer of %d seconds\n", prepaid_credit); + { + AmArg di_args,ret; + di_args.push((int)SBC_TIMER_ID_PREPAID_TIMEOUT); + di_args.push((int)prepaid_credit); // in seconds + di_args.push(getLocalTag().c_str()); + m_user_timer->invoke("setTimer", di_args, ret); + } + + { + AmArg di_args,ret; + di_args.push(call_profile.prepaid_uuid); // prepaid_uuid + di_args.push(call_profile.prepaid_acc_dest); // accounting destination + di_args.push((int)prepaid_starttime); // call start time (INVITE) + di_args.push((int)prepaid_acc_start.tv_sec); // call connect time + di_args.push(getCallID()); // Call-ID + di_args.push(getLocalTag()); // ltag + di_args.push(other_id); // other leg ltag + + prepaid_acc->invoke("connectCall", di_args, ret); + } +} + +void SBCDialog::stopPrepaidAccounting() { + if (!call_profile.prepaid_enabled) + return; + + if(prepaid_acc_start.tv_sec != 0 || prepaid_acc_start.tv_usec != 0) { + + if (NULL == prepaid_acc) { + ERROR("Internal error, trying to subtractCredit, but no prepaid_acc\n"); + return; + } + + struct timeval now; + gettimeofday(&now, NULL); + timersub(&now, &prepaid_acc_start, &now); + if(now.tv_usec > 500000) + now.tv_sec++; + DBG("Call lasted %ld seconds\n", now.tv_sec); + + AmArg di_args,ret; + di_args.push(call_profile.prepaid_uuid); // prepaid_uuid + di_args.push((int)now.tv_sec); // call duration + di_args.push(call_profile.prepaid_acc_dest); // accounting destination + di_args.push((int)prepaid_starttime); // call start time (INVITE) + di_args.push((int)prepaid_acc_start.tv_sec); // call connect time + di_args.push((int)time(NULL)); // call end time + di_args.push(getCallID()); // Call-ID + di_args.push(getLocalTag()); // ltag + di_args.push(other_id); + + prepaid_acc->invoke("subtractCredit", di_args, ret); + } +} + void SBCDialog::createCalleeSession() { SBCCalleeSession* callee_session = new SBCCalleeSession(this, call_profile); diff --git a/apps/sbc/SBC.h b/apps/sbc/SBC.h index 9af41cc2..c8214643 100644 --- a/apps/sbc/SBC.h +++ b/apps/sbc/SBC.h @@ -37,7 +37,8 @@ using std::string; -#define SBC_TIMER_ID_CALL_TIMER 1 +#define SBC_TIMER_ID_CALL_TIMER 1 +#define SBC_TIMER_ID_PREPAID_TIMEOUT 2 struct SBCCallProfile { @@ -69,6 +70,11 @@ struct SBCCallProfile { bool call_timer_enabled; string call_timer; + bool prepaid_enabled; + string prepaid_accmodule; + string prepaid_uuid; + string prepaid_acc_dest; + // todo: accounting // todo: RTP forwarding mode // todo: RTP transcoding mode @@ -123,6 +129,12 @@ class SBCDialog : public AmB2BCallerSession unsigned int call_timer; AmDynInvoke* m_user_timer; + // prepaid + AmDynInvoke* prepaid_acc; + time_t prepaid_starttime; + struct timeval prepaid_acc_start; + int prepaid_credit; + SBCCallProfile call_profile; void replaceParsedParam(const string& s, size_t p, @@ -132,6 +144,9 @@ class SBCDialog : public AmB2BCallerSession const string& app_param, AmUriParser& ruri_parser, AmUriParser& from_parser, AmUriParser& to_parser); + void stopCall(); + void startPrepaidAccounting(); + void stopPrepaidAccounting(); public: diff --git a/apps/sbc/etc/auth_b2b.sbcprofile.conf b/apps/sbc/etc/auth_b2b.sbcprofile.conf index 9f23286e..169f1a8a 100644 --- a/apps/sbc/etc/auth_b2b.sbcprofile.conf +++ b/apps/sbc/etc/auth_b2b.sbcprofile.conf @@ -41,7 +41,13 @@ message_filter=transparent #call_timer=60 # or, e.g.: call_timer=$P(t) -# set this for session timer: +## prepaid +#enable_prepaid=yes +#prepaid_accmodule=cc_acc +#prepaid_uuid=$H(P-Caller-Uuid) +#prepaid_acc_dest=$H(P-Acc-Dest) + +## session timer: #enable_session_timer=yes #session_expires=120 #minimum_timer=90 diff --git a/apps/sbc/etc/call_timer.sbcprofile.conf b/apps/sbc/etc/call_timer.sbcprofile.conf index e031c8d3..ba6e63af 100644 --- a/apps/sbc/etc/call_timer.sbcprofile.conf +++ b/apps/sbc/etc/call_timer.sbcprofile.conf @@ -43,6 +43,12 @@ call_timer=$P(t) #For a static value, set it like this #call_timer=120 +## prepaid +#enable_prepaid=yes +#prepaid_accmodule=cc_acc +#prepaid_uuid=$H(P-Caller-Uuid) +#prepaid_acc_dest=$H(P-Acc-Dest) + ## authentication: #enable_auth=yes #auth_user=$P(u) diff --git a/apps/sbc/etc/sst_b2b.sbcprofile.conf b/apps/sbc/etc/sst_b2b.sbcprofile.conf index 1cf22cc2..f95c5a98 100644 --- a/apps/sbc/etc/sst_b2b.sbcprofile.conf +++ b/apps/sbc/etc/sst_b2b.sbcprofile.conf @@ -34,6 +34,12 @@ #call_timer=60 # or, e.g.: call_timer=$P(t) +## prepaid +#enable_prepaid=yes +#prepaid_accmodule=cc_acc +#prepaid_uuid=$H(P-Caller-Uuid) +#prepaid_acc_dest=$H(P-Acc-Dest) + ## session timer: enable_session_timer=yes # if session_expires is not configured here, diff --git a/apps/sbc/etc/transparent.sbcprofile.conf b/apps/sbc/etc/transparent.sbcprofile.conf index e2fd1bde..799ca9ed 100644 --- a/apps/sbc/etc/transparent.sbcprofile.conf +++ b/apps/sbc/etc/transparent.sbcprofile.conf @@ -32,6 +32,12 @@ #call_timer=60 # or, e.g.: call_timer=$P(t) +## prepaid +#enable_prepaid=yes +#prepaid_accmodule=cc_acc +#prepaid_uuid=$H(P-Caller-Uuid) +#prepaid_acc_dest=$H(P-Acc-Dest) + ## session timer: #enable_session_timer=yes # if session_expires is not configured here, @@ -40,4 +46,4 @@ #session_expires=120 #minimum_timer=90 #session_refresh_method=UPDATE_FALLBACK_INVITE -#accept_501_reply=yes \ No newline at end of file +#accept_501_reply=yes diff --git a/doc/Readme.sbc.txt b/doc/Readme.sbc.txt index f49bc852..8c9fd27e 100644 --- a/doc/Readme.sbc.txt +++ b/doc/Readme.sbc.txt @@ -18,6 +18,8 @@ Features o Header and message filter o SIP authentication o SIP Session Timers + o call timer + o prepaid accounting SBC Profiles ------------ @@ -156,12 +158,58 @@ session_expires is set in the profile configuration file. Note that for performance reasons the whole SST configuration is in this case used from the profile configuration (it is not overwritten value-by-value). +Prepaid +------- +Prepaid accounting can be enabled with the enable_prepaid option. The credit +of an account is fetched when the initial INVITE is processed, +and a timer is set once the call is connected. When the call ends, the credit +is subtracted from the user. + +For accounting, a separate module is used. This allows to plug several types +of accounting modules. The accounting module is selected with the +prepaid_accmodule option. + +The account which is billed is taken from the prepaid_uuid option. The billing +destination is set with the prepaid_acc_dest option. + + Example: + enable_prepaid=yes + prepaid_accmodule=cc_acc + prepaid_uuid=$H(P-Caller-Uuid) + prepaid_acc_dest=$H(P-Acc-Dest) + + Here the account UUID is taken from the P-Caller-Uuid header, and the + accounting destination from the P-Acc-Dest header. + +Credit amounts are expected to be calculated in seconds. The timestamps +are presented in unix timestamp value (seconds since epoch). start_ts is +the initial INVITE timestamp, connect_ts the connect (200 OK) timestamp, +end_ts the BYE timestamp. + +Accounting interface: + getCredit(string uuid, string acc_dest, int start_ts, string call_id, string ltag) + result: int credit + + connectCall(string uuid, string acc_dest, int start_ts, int connect_ts, + string call_id, string ltag, string b_ltag) + + subtractCredit(string uuid, int call_duration, string acc_dest, int start_ts, + int connect_ts, int end_ts, string call_id, string ltag, string b_ltag) + +The cc_acc and cc_acc_xmlrpc modules may be used for accounting modules, or as starting +points for integration into custom billing systems. + +Parallel call limits can be implemented by implementing an account specific limit to the +accounting module. + + Example profiles ---------------- transparent - completely transparent B2BUA auth_b2b - identity change and SIP authentication (obsoletes auth_b2b app) sst_b2b - B2BUA with SIP Session Timers (obsoletes sst_b2b app) call_timer - call timer (obsoletes call_timer app) + prepaid - prepaid accounting (obsoletes sw_prepaid_sip app) Dependencies ------------ diff --git a/doc/Readme.sw_prepaid_sip.txt b/doc/Readme.sw_prepaid_sip.txt index 5daa66f9..869793fc 100644 --- a/doc/Readme.sw_prepaid_sip.txt +++ b/doc/Readme.sw_prepaid_sip.txt @@ -1,3 +1,10 @@ +------------------------------------------------- +This application has been obsoleted by the sbc +module and will be discontinued in the next version. +Please use the sbc module with the prepaid call +profile for the same functionality. +------------------------------------------------- + ####################################################################### # # sw_prepaid_sip, the signalling-only prepaid engine