Hi all, At my last job we developed text messaging via SIP and OpenSIPS, including billing with callcontrol. The company is now defunct and my ex-boss has asked that I contribute these patches to OpenSIPS and callcontrol. I've updated our patches so they apply to OpenSIPS SVN trunk and callcontrol darcs head. The patches are attached, hopefully they are useful to someone. To use them you want something like this:
if (is_method("MESSAGE")) { your SMS sending function here if( $avp(s:sms_status) == "accepted" ){ call_control_start(); call_control_stop(); t_reply("200", "OK"); exit; } else { call_control_stop(); t_reply("500", "Error sending SMS"); exit; } } -- bye, pabs http://bonedaddy.net/pabs3/
diff -rN -u old-callcontrol/callcontrol/controller.py new-callcontrol/callcontrol/controller.py --- old-callcontrol/callcontrol/controller.py 2012-10-14 14:51:41.145078286 +0800 +++ new-callcontrol/callcontrol/controller.py 2012-10-14 14:51:41.157078133 +0800 @@ -176,10 +176,10 @@ req.deferred.callback('Error') return self.factory.application.calls[req.callid] = call -# log.debug("Call id %s added to list of controlled calls" % (call.callid)) #DEBUG +# log.debug("%s id %s added to list of controlled calls" % ('Text' if call.type == 'text' else 'Call', call.callid)) #DEBUG else: if call.token != req.call_token: - log.error("Call id %s is duplicated" % call.callid) + log.error("%s id %s is duplicated" % ('Text' if call.type == 'text' else 'Call', call.callid)) req.deferred.callback('Duplicated callid') return # The call was previously setup which means it could be in the the users table @@ -198,7 +198,7 @@ try: call = self.factory.application.calls[req.callid] except KeyError: - log.error("Call id %s disappeared before we could finish initializing it" % req.callid) + log.error("%s id %s disappeared before we could finish initializing it" % ('Text' if call.type == 'text' else 'Call', req.callid)) req.deferred.callback('Error') else: if req.call_limit is not None and len(self.factory.application.users.get(call.billingParty, ())) == req.call_limit: @@ -206,12 +206,12 @@ call.end() req.deferred.callback('Call limit reached') elif call.locked: ## prepaid account already locked by another call - log.info("Call id %s of %s to %s forbidden because the account is locked" % (req.callid, call.user, call.ruri)) + log.info("%s id %s of %s to %s forbidden because the account is locked" % ('Text' if call.type == 'text' else 'Call', req.callid, call.user, call.ruri)) self.factory.application.clean_call(req.callid) call.end() req.deferred.callback('Locked') elif call.timelimit == 0: ## prepaid account with no credit - log.info("Call id %s of %s to %s forbidden because credit is too low" % (req.callid, call.user, call.ruri)) + log.info("%s id %s of %s to %s forbidden because credit is too low" % ('Text' if call.type == 'text' else 'Call', req.callid, call.user, call.ruri)) self.factory.application.clean_call(req.callid) call.end() req.deferred.callback('No credit') @@ -220,7 +220,7 @@ self.factory.application.users.setdefault(call.billingParty, []).append(call.callid) req.deferred.callback('Limited') else: ## no limit for call - log.info("Call id %s of %s to %s is postpaid not limited" % (req.callid, call.user, call.ruri)) + log.info("%s id %s of %s to %s is postpaid not limited" % ('Text' if call.type == 'text' else 'Call', req.callid, call.user, call.ruri)) self.factory.application.clean_call(req.callid) call.end() req.deferred.callback('No limit') @@ -253,12 +253,12 @@ if req.show == 'sessions': for callid, call in self.factory.application.calls.items(): if not req.user or call.user.startswith(req.user): - debuglines.append('Call id %s of %s to %s: %s' % (callid, call.user, call.ruri, call.status)) + debuglines.append('%s id %s of %s to %s: %s' % ('Text' if call.type == 'text' else 'Call', callid, call.user, call.ruri, call.status)) elif req.show == 'session': try: call = self.factory.application.calls[req.callid] except KeyError: - debuglines.append('Call id %s does not exist' % req.callid) + debuglines.append('Call id %s does not exist' % (req.callid) else: for key, value in call.items(): debuglines.append('%s: %s' % (key, value)) @@ -318,7 +318,7 @@ self.engines.remove_user(call.billingParty) except (ValueError, KeyError): pass -# log.debug("Call id %s removed from the list of controlled calls" % callid) #DEBUG +# log.debug("%s id %s removed from the list of controlled calls" % ('Text' if call.type == 'text' else 'Call', callid)) #DEBUG def run(self): @@ -469,7 +469,7 @@ class Request(object): """A request parsed into a structure based on request type""" - __methods = {'init': ('callid', 'diverter', 'ruri', 'sourceip', 'from'), + __methods = {'init': ('type', 'callid', 'diverter', 'ruri', 'sourceip', 'from'), 'start': ('callid', 'dialogid'), 'stop': ('callid',), 'debug': ('show',), @@ -541,7 +541,7 @@ def __str__(self): if self.cmd == 'init': - return "%(cmd)s: callid=%(callid)s from=%(from_)s ruri=%(ruri)s diverter=%(diverter)s sourceip=%(sourceip)s prepaid=%(prepaid)s call_limit=%(call_limit)s" % self.__dict__ + return "%(cmd)s: type=%(type)s callid=%(callid)s from=%(from_)s ruri=%(ruri)s diverter=%(diverter)s sourceip=%(sourceip)s prepaid=%(prepaid)s call_limit=%(call_limit)s" % self.__dict__ elif self.cmd == 'start': return "%(cmd)s: callid=%(callid)s dialogid=%(dialogid)s" % self.__dict__ elif self.cmd == 'stop': diff -rN -u old-callcontrol/callcontrol/rating.py new-callcontrol/callcontrol/rating.py --- old-callcontrol/callcontrol/rating.py 2012-10-14 14:51:41.145078286 +0800 +++ new-callcontrol/callcontrol/rating.py 2012-10-14 14:51:41.157078133 +0800 @@ -287,6 +287,8 @@ args = {} if call.inprogress: args['State'] = 'Connected' + if call.type == 'text': + args['applicationType'] = 'text' req = RatingRequest('MaxSessionTime', reliable=reliable, CallId=call.callid, From=call.billingParty, To=call.ruri, Gateway=call.sourceip, Duration=max_duration, **args) if self.connection is not None: @@ -296,8 +298,13 @@ return req.deferred def debitBalance(self, call, reliable=True): + args = {} + if call.type == 'text': + if call.duration > 0.0: + call.duration = 1.0 + args['applicationType'] = 'text' req = RatingRequest('DebitBalance', reliable=reliable, CallId=call.callid, From=call.billingParty, To=call.ruri, - Gateway=call.sourceip, Duration=call.duration) + Gateway=call.sourceip, Duration=call.duration, **args) if self.connection is not None: return self.connection.protocol.send_request(req).deferred else: diff -rN -u old-callcontrol/callcontrol/sip.py new-callcontrol/callcontrol/sip.py --- old-callcontrol/callcontrol/sip.py 2012-10-14 14:51:41.149078235 +0800 +++ new-callcontrol/callcontrol/sip.py 2012-10-14 14:51:41.157078133 +0800 @@ -94,6 +94,7 @@ """Defines a call""" def __init__(self, request, application): Structure.__init__(self) + self.type = request.type self.prepaid = request.prepaid self.locked = False ## if the account is locked because another call is in progress self.expired = False ## if call did consume its timelimit before being terminated @@ -126,7 +127,7 @@ self.application = application def __str__(self): - return ("callid=%(callid)s from=%(from)s ruri=%(ruri)s " + return ("type=%(type)s callid=%(callid)s from=%(from)s ruri=%(ruri)s " "diverter=%(diverter)s sourceip=%(sourceip)s " "timelimit=%(timelimit)s status=%%s" % self % self.status) @@ -202,10 +203,11 @@ def start(self, request): assert self.__initialized, "Trying to start an unitialized call" if self.starttime is None: - self.dialogid = DialogID(request.dialogid) + if self.type != 'text': + self.dialogid = DialogID(request.dialogid) self.starttime = time.time() if self.timer is not None: - log.info("Call id %s of %s to %s started for maximum %d seconds" % (self.callid, self.user, self.ruri, self.timelimit)) + log.info("%s id %s of %s to %s started for maximum %d seconds" % ('Text' if self.type == 'text' else 'Call',self.callid, self.user, self.ruri, self.timelimit)) self.timer.start() # also reset all calls of user to this call's timelimit # no reason to alter other calls if this call is not prepaid @@ -222,7 +224,7 @@ call.timelimit = self.starttime - call.starttime + self.timelimit if call.timer: call.timer.reset(self.timelimit) - log.info("Call id %s of %s to %s also set to %d seconds" % (callid, call.user, call.ruri, self.timelimit)) + log.info("%s id %s of %s to %s also set to %d seconds" % ('Text' if call.type == 'text' else 'Call', callid, call.user, call.ruri, self.timelimit)) elif not call.complete: call.timelimit = self.timelimit call._setup_timer() @@ -238,13 +240,13 @@ call.timelimit += delay if call.timer: call.timer.delay(delay) - log.info("Call id %s of %s to %s %s maximum %d seconds" % (callid, call.user, call.ruri, (call is self) and 'connected for' or 'previously connected set to', limit)) + log.info("%s id %s of %s to %s %s maximum %d seconds" % ('Text' if call.type == 'text' else 'Call', callid, call.user, call.ruri, (call is self) and 'connected for' or 'previously connected set to', limit)) elif not call.complete: call.timelimit = self.timelimit call._setup_timer() def _start_error(self, fail): - log.info("Could not get call limit for call id %s of %s to %s" % (self.callid, self.user, self.ruri)) + log.info("Could not get call limit for %s id %s of %s to %s" % ('text' if call.type == 'text' else 'call', self.callid, self.user, self.ruri)) def end(self, calltime=None, reason=None, sendbye=False): if sendbye and self.dialogid is not None: @@ -261,7 +263,7 @@ ## we were notified of this and we have the actual call duration in `calltime' #self.endtime = self.starttime + calltime self.duration = calltime - log.info("Call id %s of %s to %s was already disconnected (ended or did timeout) after %s seconds" % (self.callid, self.user, self.ruri, self.duration)) + log.info("%s id %s of %s to %s was already disconnected (ended or did timeout) after %s seconds" % ('Text' if self.type == 'text' else 'Call',self.callid, self.user, self.ruri, self.duration)) elif self.expired: self.duration = self.timelimit if duration > self.timelimit + 10: @@ -273,7 +275,7 @@ rating = RatingEngineConnections.getConnection(self) rating.debitBalance(self).addCallbacks(callback=self._end_finish, errback=self._end_error, callbackArgs=[reason and fullreason or None]) elif reason is not None: - log.info("Call id %s of %s to %s %s%s" % (self.callid, self.user, self.ruri, fullreason, self.duration and (' after %d seconds' % self.duration) or '')) + log.info("%s id %s of %s to %s %s%s" % ('Text' if self.type == 'text' else 'Call',self.callid, self.user, self.ruri, fullreason, self.duration and (' after %d seconds' % self.duration) or '')) def _end_finish(self, (timelimit, value), reason): if timelimit is not None and timelimit > 0: @@ -285,19 +287,19 @@ if call.inprogress: call.timelimit = now - call.starttime + timelimit if call.timer: - log.info("Call id %s of %s to %s previously connected set to %d seconds" % (callid, call.user, call.ruri, timelimit)) + log.info("%s id %s of %s to %s previously connected set to %d seconds" % ('Text' if call.type == 'text' else 'Call', callid, call.user, call.ruri, timelimit)) call.timer.reset(timelimit) elif not call.complete: call.timelimit = timelimit call._setup_timer() # log ended call if self.duration > 0: - log.info("Call id %s of %s to %s %s after %d seconds, call price is %s" % (self.callid, self.user, self.ruri, reason, self.duration, value)) + log.info("%s id %s of %s to %s %s after %d seconds, call price is %s" % ('Text' if call.type == 'text' else 'Call', self.callid, self.user, self.ruri, reason, self.duration, value)) elif reason is not None: - log.info("Call id %s of %s to %s %s" % (self.callid, self.user, self.ruri, reason)) + log.info("%s id %s of %s to %s %s" % ('Text' if call.type == 'text' else 'Call', self.callid, self.user, self.ruri, reason)) def _end_error(self, fail): - log.info("Could not debit balance for call id %s of %s to %s" % (self.callid, self.user, self.ruri)) + log.info("Could not debit balance for %s id %s of %s to %s" % ('text' if call.type == 'text' else 'call', self.callid, self.user, self.ruri)) status = property(lambda self: self.inprogress and 'in-progress' or 'pending') complete = property(lambda self: self.dialogid is not None)
Index: modules/call_control/call_control.c =================================================================== --- modules/call_control/call_control.c (revision 9319) +++ modules/call_control/call_control.c (working copy) @@ -121,6 +121,7 @@ int parse_param_init(unsigned int type, void *val); int parse_param_start(unsigned int type, void *val); int parse_param_stop(unsigned int type, void *val); +int parse_param_text(unsigned int type, void *val); /* Local global variables */ static CallControlSocket callcontrol_socket = { @@ -153,7 +154,7 @@ struct dlg_binds dlg_api; static int prepaid_account_flag = -1; -AVP_List *init_avps = NULL, *start_avps = NULL, *stop_avps = NULL; +AVP_List *init_avps = NULL, *start_avps = NULL, *stop_avps = NULL, *text_avps = NULL; pv_elem_t *model; @@ -166,6 +167,7 @@ {"init", STR_PARAM|USE_FUNC_PARAM, (void*)parse_param_init}, {"start", STR_PARAM|USE_FUNC_PARAM, (void*)parse_param_start}, {"stop", STR_PARAM|USE_FUNC_PARAM, (void*)parse_param_stop}, + {"text", STR_PARAM|USE_FUNC_PARAM, (void*)parse_param_text}, {"disable", INT_PARAM, &disable}, {"socket_name", STR_PARAM, &(callcontrol_socket.name)}, {"socket_timeout", INT_PARAM, &(callcontrol_socket.timeout)}, @@ -199,7 +201,8 @@ typedef enum CallControlAction { CAInitialize = 1, CAStart, - CAStop + CAStop, + CAText } CallControlAction; @@ -226,6 +229,7 @@ str call_token; char* prepaid_account; int call_limit; + Bool text; } CallInfo; @@ -339,8 +343,15 @@ return 0; } +int +parse_param_text(unsigned int type, void *val) { + if (parse_param(val, &text_avps) == -1) + return E_CFG; + return 0; +} + // Message checking and parsing // @@ -507,6 +518,9 @@ case CAStop: headers = HDR_CALLID_F; break; + case CAText: + headers = HDR_CALLID_F|HDR_FROM_F; + break; default: // Invalid action. Should never get here. assert(False); @@ -568,6 +582,12 @@ } } + if (action == CAText) { + call_info.ruri = get_canonical_request_uri(msg); + call_info.diverter = get_diverter(msg); + call_info.source_ip = get_signaling_ip(msg); + } + call_info.action = action; return &call_info; @@ -591,6 +611,9 @@ case CAStop: al = stop_avps; break; + case CAText: + al = text_avps; + break; default: // should never get here, but keep gcc from complaining assert(False); @@ -686,6 +709,30 @@ break; + case CAText: + len = snprintf(request, sizeof(request), + "text\r\n" + "ruri: %.*s\r\n" + "diverter: %.*s\r\n" + "sourceip: %.*s\r\n" + "callid: %.*s\r\n" + "from: %.*s\r\n" + "fromtag: %.*s\r\n" + "\r\n", + call->ruri.len, call->ruri.s, + call->diverter.len, call->diverter.s, + call->source_ip.len, call->source_ip.s, + call->callid.len, call->callid.s, + call->from.len, call->from.s, + call->from_tag.len, call->from_tag.s); + + if (len >= sizeof(request)) { + LM_ERR("callcontrol request is longer than %ld bytes\n", (unsigned long)sizeof(request)); + return NULL; + } + + break; + default: // should never get here, but keep gcc from complaining assert(False); @@ -976,7 +1023,48 @@ } } +// Text processing +// +// Return codes: +// 1 - Allowed +// -1 - No credit +// -5 - Internal error (message parsing, communication, ...) +static int +call_control_text(struct sip_msg *msg) +{ + CallInfo *call; + char *message, *result = NULL; + + + call = get_call_info(msg, CAText); + if (!call) { + LM_ERR("can't retrieve text info\n"); + return -5; + } + + + if (!text_avps) + message = make_default_request(call); + else + message = make_custom_request(msg, call); + + if (!message) + return -5; + + result = send_command(message); + + if (result==NULL) { + return -5; + } else if (strcasecmp(result, "Allowed\r\n")==0) { + return 1; + } else if (strcasecmp(result, "No credit\r\n")==0) { + return -1; + } else { + return -5; + } +} + // Dialog callbacks and helpers // @@ -1022,7 +1110,7 @@ // Return codes: // 2 - No limit -// 1 - Limited +// 1 - Limited/Allowed // -1 - No credit // -2 - Locked // -3 - Duplicated callid @@ -1038,10 +1126,13 @@ if (disable) return 2; - if (msg->first_line.type!=SIP_REQUEST || msg->REQ_METHOD!=METHOD_INVITE || has_to_tag(msg)) { + if (msg->first_line.type!=SIP_REQUEST || (msg->REQ_METHOD!=METHOD_INVITE && msg->REQ_METHOD!=METHOD_MESSAGE) || has_to_tag(msg)) { LM_WARN("call_control should only be called for the first INVITE\n"); return -5; } + + if(msg->REQ_METHOD==METHOD_MESSAGE) + return call_control_text(msg); result = call_control_initialize(msg); if (result == 1) { @@ -1207,6 +1298,9 @@ if (stop_avps) destroy_list(stop_avps); + + if (text_avps) + destroy_list(stop_avps); }
signature.asc
Description: This is a digitally signed message part
_______________________________________________ Users mailing list Users@lists.opensips.org http://lists.opensips.org/cgi-bin/mailman/listinfo/users