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);
 }
 
 

Attachment: 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

Reply via email to