Esempio n. 1
0
    def __init__(self, SMPPClientSMListenerConfig, SMPPClientFactory,
                 amqpBroker, redisClient):
        self.config = SMPPClientSMListenerConfig
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(
            self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.qosTimer = None

        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(
            self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = logging.FileHandler(filename=self.config.log_file)
            formatter = logging.Formatter(self.config.log_format,
                                          self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False
Esempio n. 2
0
    def setUp(self):
        LongSubmitSmWithSARTestCase.setUp(self)

        # Reset opFactory with long_content_max_parts = 8
        self.opFactory = SMPPOperationFactory(self.config,
                                              long_content_max_parts=8)
        self.long_content_max_parts = self.opFactory.long_content_max_parts
Esempio n. 3
0
    def __init__(self, config, SMPPClientFactory, amqpBroker, redisClient, RouterPB=None, interceptorpb_client=None):
        self.config = config
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.RouterPB = RouterPB
        self.interceptorpb_client = interceptorpb_client
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.submit_retrials = {}
        self.qosTimer = None

        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False
Esempio n. 4
0
 def setUp(self):
     self.opFactory = SMPPOperationFactory(SMPPClientConfig(id='test-id'))
     self.user = User(1, Group(100), 'username', 'password')
     self.pdu = self.opFactory.SubmitSM(
         source_addr=b'2',
         destination_addr=b'3',
         short_message=b'sunny day !',
     )
Esempio n. 5
0
    def send_long_submit_sm(self, long_content_split):
        """Reference to #27:
        When sending a long SMS, logger must write concatenated content
        """
        lc = LogCapture("jasmin-sm-listener")
        yield self.connect('127.0.0.1', self.pbPort)

        yield self.add(self.defaultConfig)
        yield self.start(self.defaultConfig.id)

        # Wait for 'BOUND_TRX' state
        yield waitFor(2)

        # Build a long submit_sm
        assertionKey = str(randint(10, 99)) * 100 + 'EOF' # 203 chars
        config = SMPPClientConfig(id='defaultId')
        opFactory = SMPPOperationFactory(config, long_content_split = long_content_split)
        SubmitSmPDU = opFactory.SubmitSM(
            source_addr='1423',
            destination_addr='98700177',
            short_message=assertionKey,
        )

        # Send submit_sm
        yield self.submit_sm(self.defaultConfig.id, SubmitSmPDU)

        # Wait 2 seconds
        yield waitFor(2)

        yield self.stop(self.defaultConfig.id)

        # Wait for unbound state
        yield waitFor(2)


        # Assertions
        # Take the lastClient (and unique one) and assert received messages
        self.assertEqual(len(self.SMSCPort.factory.lastClient.submitRecords), 2)
        if long_content_split == 'udh':
            concatenatedShortMessage = self.SMSCPort.factory.lastClient.submitRecords[0].params['short_message'][6:]
            concatenatedShortMessage+= self.SMSCPort.factory.lastClient.submitRecords[1].params['short_message'][6:]
        else:
            concatenatedShortMessage = self.SMSCPort.factory.lastClient.submitRecords[0].params['short_message']
            concatenatedShortMessage+= self.SMSCPort.factory.lastClient.submitRecords[1].params['short_message']
        self.assertEqual(concatenatedShortMessage, assertionKey)
        # Logged concatenated message
        loggedSms = False
        for record in lc.records:
            if record.getMessage()[:6] == 'SMS-MT':
                loggedSms = True
                # Will raise ValueError if concatenatedShortMessage is not logged
                record.getMessage().index('[content:%s]' % concatenatedShortMessage)
                break
        # This will assert if we had a SMS-MT logged
        self.assertTrue(loggedSms)
        # There were a connection to the SMSC
        self.assertTrue(self.SMSCPort.factory.buildProtocol.called)
        self.assertEqual(self.SMSCPort.factory.buildProtocol.call_count, 1)
Esempio n. 6
0
    def __init__(self, config):
        self.log_category = "jasmin-dlr-thrower"
        self.exchangeName = 'messaging'
        self.consumerTag = 'DLRThrower'
        self.routingKey = 'dlr_thrower.*'
        self.queueName = 'dlr_thrower'
        self.callback = self.dlr_throwing_callback
        self.opFactory = SMPPOperationFactory()

        Thrower.__init__(self, config)
Esempio n. 7
0
    def __init__(self, HTTPApiConfig, RouterPB, stats, log):
        Resource.__init__(self)
        
        self.RouterPB = RouterPB
        self.log = log
        self.stats = stats

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(long_content_max_parts = HTTPApiConfig.long_content_max_parts,
                                              long_content_split = HTTPApiConfig.long_content_split)
Esempio n. 8
0
    def setUp(self):
        yield SMSCSimulatorRecorder.setUp(self)

        self.SMSCPort.factory.buildProtocol = mock.Mock(wraps=self.SMSCPort.factory.buildProtocol)

        config = SMPPClientConfig(id='defaultId')
        opFactory = SMPPOperationFactory(config)
        self.SubmitSmPDU = opFactory.SubmitSM(
            source_addr='1423',
            destination_addr='98700177',
            short_message='Hello world !',
        )
Esempio n. 9
0
    def setUp(self):
        yield SMSCSimulatorRecorder.setUp(self)

        self.SMSCPort.factory.buildProtocol = mock.Mock(wraps=self.SMSCPort.factory.buildProtocol)

        config = SMPPClientConfig(id='defaultId')
        opFactory = SMPPOperationFactory(config)
        self.SubmitSmPDU = opFactory.SubmitSM(
            source_addr='1423',
            destination_addr='06155423',
            short_message='Hello world !',
        )

        self.SubmitSmBill = SubmitSmBill(User('test_user', Group('test_group'), 'test_username', 'pwd'))
Esempio n. 10
0
    def setUp(self):
        LongSubmitSmTestCase.setUp(self)

        # Reset opFactory with long_content_split = 'udh'
        self.opFactory = SMPPOperationFactory(self.config,
                                              long_content_split='udh')
        self.long_content_max_parts = self.opFactory.long_content_max_parts
Esempio n. 11
0
    def __init__(self, config, SMPPClientFactory, amqpBroker, redisClient, RouterPB=None, interceptorpb_client=None):
        self.config = config
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.RouterPB = RouterPB
        self.interceptorpb_client = interceptorpb_client
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.submit_retrials = {}
        self.qosTimer = None

        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False
Esempio n. 12
0
    def __init__(self, config):
        self.log_category = "jasmin-dlr-thrower"
        self.exchangeName = 'messaging'
        self.consumerTag = 'DLRThrower'
        self.routingKey = 'dlr_thrower.*'
        self.queueName = 'dlr_thrower'
        self.callback = self.dlr_throwing_callback
        self.opFactory = SMPPOperationFactory()

        Thrower.__init__(self, config)
Esempio n. 13
0
    def setUp(self):
        self.factory = Factory()
        self.factory.protocol = self.protocol
        self.port = reactor.listenTCP(9001, self.factory)
        self.testPort = self.port.getHost().port

        args = self.configArgs.copy()
        args['host'] = self.configArgs.get('host', 'localhost')
        args['port'] = self.configArgs.get('port', self.testPort)
        args['username'] = self.configArgs.get('username', 'anyusername')
        args['password'] = self.configArgs.get('password', '')
        args['log_level'] = self.configArgs.get('log_level', logging.DEBUG)
        self.config = SMPPClientConfig(**args)

        self.opFactory = SMPPOperationFactory(self.config)
Esempio n. 14
0
    def setUp(self):
        self.factory = Factory()
        self.factory.protocol = self.protocol

        args = self.configArgs.copy()
        args['host'] = self.configArgs.get('host', 'localhost')
        args['port'] = self.configArgs.get('port', '2775')
        args['username'] = self.configArgs.get('username', 'anyusername')
        args['password'] = self.configArgs.get('password', '')
        args['log_level'] = self.configArgs.get('log_level', logging.DEBUG)

        self.config = SMPPClientConfig(**args)
        self.opFactory = SMPPOperationFactory(self.config)

        # Start listening 5 seconds later, the client shall successfully reconnect
        reactor.callLater(5, self.startListening, self.config.port)
Esempio n. 15
0
    def __init__(self, SMPPClientSMListenerConfig, SMPPClientFactory, amqpBroker, redisClient):
        self.config = SMPPClientSMListenerConfig
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.qosTimer = None
        
        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = logging.FileHandler(filename=self.config.log_file)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
Esempio n. 16
0
class VeryLongSubmitSmUsingSARTestCase(LongSubmitSmWithSARTestCase):
    configArgs = {
        'id': 'test-id',
        'sessionInitTimerSecs': 0.1,
        'reconnectOnConnectionFailure': False,
        'reconnectOnConnectionLoss': False,
        'username': '******',
    }

    def setUp(self):
        LongSubmitSmWithSARTestCase.setUp(self)

        # Reset opFactory with long_content_max_parts = 8
        self.opFactory = SMPPOperationFactory(self.config,
                                              long_content_max_parts=8)
        self.long_content_max_parts = self.opFactory.long_content_max_parts

    @defer.inlineCallbacks
    def test_long_submit_sm_7bit(self):
        client = SMPPClientFactory(self.config)
        # Connect and bind
        yield client.connectAndBind()
        smpp = client.smpp
        self.prepareMocks(smpp)

        # Send submit_sm
        content = self.composeMessage(GSM0338, 1224)  # 1224 = 153 * 8
        SubmitSmPDU = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=content,
            data_coding=0,
        )
        yield smpp.sendDataRequest(SubmitSmPDU)

        # Unbind & Disconnect
        yield smpp.unbindAndDisconnect()

        ##############
        # Assertions :
        self.runAsserts(smpp, content, len(content) / 153)

    @defer.inlineCallbacks
    def test_very_long_submit_sm_7bit(self):
        client = SMPPClientFactory(self.config)
        # Connect and bind
        yield client.connectAndBind()
        smpp = client.smpp
        self.prepareMocks(smpp)

        # Send submit_sm
        content = self.composeMessage(GSM0338, 1530)  # 1530 = 153 * 10
        SubmitSmPDU = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=content,
            data_coding=0,
        )
        yield smpp.sendDataRequest(SubmitSmPDU)

        # Unbind & Disconnect
        yield smpp.unbindAndDisconnect()

        ##############
        # Assertions :
        self.assertEquals(self.long_content_max_parts + 1,
                          smpp.PDUReceived.call_count)
        self.assertEquals(self.long_content_max_parts + 1,
                          smpp.sendPDU.call_count)

    @defer.inlineCallbacks
    def test_long_submit_sm_8bit(self):
        client = SMPPClientFactory(self.config)
        # Connect and bind
        yield client.connectAndBind()
        smpp = client.smpp
        self.prepareMocks(smpp)

        # Send submit_sm
        content = self.composeMessage(ISO8859_1, 1072)  # 1072 = 134 * 8
        SubmitSmPDU = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=content,
            data_coding=3,
        )
        yield smpp.sendDataRequest(SubmitSmPDU)

        # Unbind & Disconnect
        yield smpp.unbindAndDisconnect()

        ##############
        # Assertions :
        self.runAsserts(smpp, content, len(content) / 134)

    @defer.inlineCallbacks
    def test_very_long_submit_sm_8bit(self):
        client = SMPPClientFactory(self.config)
        # Connect and bind
        yield client.connectAndBind()
        smpp = client.smpp
        self.prepareMocks(smpp)

        # Send submit_sm
        content = self.composeMessage(ISO8859_1, 1340)  # 1340 = 134 * 10
        SubmitSmPDU = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=content,
            data_coding=3,
        )
        yield smpp.sendDataRequest(SubmitSmPDU)

        # Unbind & Disconnect
        yield smpp.unbindAndDisconnect()

        ##############
        # Assertions :
        self.assertEquals(self.long_content_max_parts + 1,
                          smpp.PDUReceived.call_count)
        self.assertEquals(self.long_content_max_parts + 1,
                          smpp.sendPDU.call_count)

    @defer.inlineCallbacks
    def test_long_submit_sm_16bit(self):
        client = SMPPClientFactory(self.config)
        # Connect and bind
        yield client.connectAndBind()
        smpp = client.smpp
        self.prepareMocks(smpp)

        # Send submit_sm
        UCS2 = {'\x0623', '\x0631', '\x0646', '\x0628'}
        content = self.composeMessage(UCS2, 536)  # 536 = 67 * 8
        SubmitSmPDU = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=content,
            data_coding=8,
        )
        yield smpp.sendDataRequest(SubmitSmPDU)

        # Unbind & Disconnect
        yield smpp.unbindAndDisconnect()

        ##############
        # Assertions :
        self.runAsserts(smpp, content, len(content) / 67)

    @defer.inlineCallbacks
    def test_very_long_submit_sm_16bit(self):
        client = SMPPClientFactory(self.config)
        # Connect and bind
        yield client.connectAndBind()
        smpp = client.smpp
        self.prepareMocks(smpp)

        # Send submit_sm
        UCS2 = {'\x0623', '\x0631', '\x0646', '\x0628'}
        content = self.composeMessage(UCS2, 3350)  # 3350 = 67 * 10
        SubmitSmPDU = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=content,
            data_coding=8,
        )
        yield smpp.sendDataRequest(SubmitSmPDU)

        # Unbind & Disconnect
        yield smpp.unbindAndDisconnect()

        ##############
        # Assertions :
        self.assertEquals(self.long_content_max_parts + 1,
                          smpp.PDUReceived.call_count)
        self.assertEquals(self.long_content_max_parts + 1,
                          smpp.sendPDU.call_count)
Esempio n. 17
0
class SMPPClientSMListener:
    debug_it = {'rejectCount': 0}
    '''
    This is a listener object instanciated for every new SMPP connection, it is responsible of handling 
    SubmitSm, DeliverSm and SubmitSm PDUs for a given SMPP connection
    '''
    
    def __init__(self, SMPPClientSMListenerConfig, SMPPClientFactory, amqpBroker, redisClient):
        self.config = SMPPClientSMListenerConfig
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.qosTimer = None
        
        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = logging.FileHandler(filename=self.config.log_file)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

    def setSubmitSmQ(self, queue):
        self.log.debug('Setting a new submit_sm_q: %s' % queue)
        self.submit_sm_q = queue
        
    def clearRejectTimer(self, msgid):
        if msgid in self.rejectTimers:
            t = self.rejectTimers[msgid]
            if t.active():
                t.cancel()
            del self.rejectTimers[msgid]

    def clearRejectTimers(self):
        for msgid, timer in self.rejectTimers.items():
            if timer.active():
                timer.cancel()
            del self.rejectTimers[msgid]
                
    def clearQosTimer(self):
        if self.qosTimer is not None and self.qosTimer.called == False:
            self.qosTimer.cancel()
            self.qosTimer = None
        
    def clearAllTimers(self):
        self.clearQosTimer()
        self.clearRejectTimers()
    
    @defer.inlineCallbacks
    def rejectAndRequeueMessage(self, message, delay = True):
        msgid = message.content.properties['message-id']
        
        if delay != False:
            self.log.debug("Requeuing SubmitSmPDU[%s] in %s seconds" % 
                           (msgid, self.SMPPClientFactory.config.requeue_delay))

            # Use configured requeue_delay or specific one
            if delay is not bool:
                requeue_delay = delay
            else:
                requeue_delay = self.SMPPClientFactory.config.requeue_delay

            # Requeue the message with a delay
            t = reactor.callLater(requeue_delay, 
                                  self.rejectMessage,
                                  message = message, 
                                  requeue = 1)

            # If any, clear timer before setting a new one
            self.clearRejectTimer(msgid)
            
            self.rejectTimers[msgid] = t
            defer.returnValue(t)
        else:
            self.log.debug("Requeuing SubmitSmPDU[%s] without delay" % msgid)
            yield self.rejectMessage(message, requeue = 1)
    @defer.inlineCallbacks
    def rejectMessage(self, message, requeue = 0):
        yield self.amqpBroker.chan.basic_reject(delivery_tag=message.delivery_tag, requeue = requeue)
    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)
    
    @defer.inlineCallbacks
    def setKeyExpiry(self, callbackArg, key, expiry):
        yield self.redisClient.expire(key, expiry)

    @defer.inlineCallbacks
    def submit_sm_callback(self, message):
        """This callback is a queue listener
        it is called whenever a message was consumed from queue
        c.f. test_amqp.ConsumeTestCase for use cases
        """
        msgid = message.content.properties['message-id']
        SubmitSmPDU = pickle.loads(message.content.body)

        self.submit_sm_q.get().addCallback(self.submit_sm_callback).addErrback(self.submit_sm_errback)

        self.log.debug("Callbacked a submit_sm with a SubmitSmPDU[%s] (?): %s" % (msgid, SubmitSmPDU))

        if self.qos_last_submit_sm_at is None:
            self.qos_last_submit_sm_at = datetime(1970, 1, 1)    
            
        if self.SMPPClientFactory.config.submit_sm_throughput > 0:
            # QoS throttling
            qos_throughput_second = 1 / float(self.SMPPClientFactory.config.submit_sm_throughput)
            qos_throughput_ysecond_td = timedelta( microseconds = qos_throughput_second * 1000000)
            qos_delay = datetime.now() - self.qos_last_submit_sm_at
            if qos_delay < qos_throughput_ysecond_td:
                qos_slow_down = float((qos_throughput_ysecond_td - qos_delay).microseconds) / 1000000
                # We're faster than submit_sm_throughput, slow down before taking a new message from the queue
                self.log.debug("QoS: submit_sm_callback is faster (%s) than fixed throughput (%s), slowing down by %s seconds (message will be requeued)." % (
                                qos_delay,
                                qos_throughput_ysecond_td,
                                qos_slow_down
                                ))

                # Relaunch queue callbacking after qos_slow_down seconds
                #self.qosTimer = task.deferLater(reactor, qos_slow_down, self.submit_sm_q.get)
                #self.qosTimer.addCallback(self.submit_sm_callback).addErrback(self.submit_sm_errback)
                # Requeue the message
                yield self.rejectAndRequeueMessage(message, delay = qos_slow_down)
                defer.returnValue(False)
            
            self.qos_last_submit_sm_at = datetime.now()
        
        # Verify if message is a SubmitSm PDU
        if isinstance(SubmitSmPDU, SubmitSM) == False:
            self.log.error("Received an object[%s] which is not an instance of SubmitSm: discarding this unkown object from the queue" % msgid)
            yield self.rejectMessage(message)
            defer.returnValue(False)
        # If the message has expired in the queue
        if 'headers' in message.content.properties and 'expiration' in message.content.properties['headers']:
            expiration_datetime = parser.parse(message.content.properties['headers']['expiration'])
            if expiration_datetime < datetime.now():
                self.log.info("Discarding expired message[%s]: expiration is %s" % (msgid, expiration_datetime))
                yield self.rejectMessage(message)
                defer.returnValue(False)
        # SMPP Client should be already connected
        if self.SMPPClientFactory.smpp == None:
            self.log.error("SMPP Client is not connected: requeuing SubmitSmPDU[%s]" % msgid)
            yield self.rejectAndRequeueMessage(message)
            defer.returnValue(False)
        # SMPP Client should be already bound as transceiver or transmitter
        if self.SMPPClientFactory.smpp.isBound() == False:
            self.log.error("SMPP Client is not bound: Requeuing SubmitSmPDU[%s]" % msgid)
            yield self.rejectAndRequeueMessage(message)
            defer.returnValue(False)

        self.log.debug("Sending SubmitSmPDU through SMPPClientFactory")
        yield self.SMPPClientFactory.smpp.sendDataRequest(
                                                          SubmitSmPDU
                                                          ).addCallback(self.submit_sm_resp_event, message)

    @defer.inlineCallbacks
    def submit_sm_resp_event(self, r, amqpMessage):
        msgid = amqpMessage.content.properties['message-id']
        total_bill_amount = None
        
        if ('headers' not in amqpMessage.content.properties or 
            'submit_sm_resp_bill' not in amqpMessage.content.properties['headers']):
            submit_sm_resp_bill = None
        else:  
            submit_sm_resp_bill = pickle.loads(amqpMessage.content.properties['headers']['submit_sm_resp_bill'])
        
        if r.response.status == CommandStatus.ESME_ROK:
            # Get bill information
            total_bill_amount = 0.0
            if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts() > 0:
                total_bill_amount = submit_sm_resp_bill.getTotalAmounts()

            # UDH is set ?
            UDHI_INDICATOR_SET = False
            if hasattr(r.request.params['esm_class'], 'gsmFeatures'):
                for gsmFeature in r.request.params['esm_class'].gsmFeatures:
                    if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                        UDHI_INDICATOR_SET = True
                        break

            # What type of splitting ?
            splitMethod = None
            if 'sar_msg_ref_num' in r.request.params:
                splitMethod = 'sar'
            elif UDHI_INDICATOR_SET and r.request.params['short_message'][:3] == '\x05\x00\x03':
                splitMethod = 'udh'
            
            # Concatenate short_message
            if splitMethod is not None:
                _pdu = r.request
                if splitMethod == 'sar':
                    short_message = _pdu.params['short_message']
                else:
                    short_message = _pdu.params['short_message'][6:]
                
                while hasattr(_pdu, 'nextPdu'):
                    _pdu = _pdu.nextPdu
                    if splitMethod == 'sar':
                        short_message += _pdu.params['short_message']
                    else:
                        short_message += _pdu.params['short_message'][6:]
                    
                    # Increase bill amount for each submit_sm_resp
                    if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts() > 0:
                        total_bill_amount+= submit_sm_resp_bill.getTotalAmounts()
            else:
                short_message = r.request.params['short_message']
            
            self.log.info("SMS-MT [cid:%s] [queue-msgid:%s] [smpp-msgid:%s] [status:%s] [prio:%s] [dlr:%s] [validity:%s] [from:%s] [to:%s] [content:%s]" % 
                          (
                           self.SMPPClientFactory.config.id,
                           msgid,
                           r.response.params['message_id'],
                           r.response.status,
                           amqpMessage.content.properties['priority'],
                           r.request.params['registered_delivery'].receipt,
                           'none' if ('headers' not in amqpMessage.content.properties 
                                      or 'expiration' not in amqpMessage.content.properties['headers']) 
                                  else amqpMessage.content.properties['headers']['expiration'],
                           r.request.params['source_addr'],
                           r.request.params['destination_addr'],
                           short_message
                           ))
        else:
            self.log.info("SMS-MT [cid:%s] [queue-msgid:%s] [status:ERROR/%s] [prio:%s] [dlr:%s] [validity:%s] [from:%s] [to:%s] [content:%s]" % 
                          (
                           self.SMPPClientFactory.config.id,
                           msgid,
                           r.response.status,
                           amqpMessage.content.properties['priority'],
                           r.request.params['registered_delivery'].receipt,
                           'none' if ('headers' not in amqpMessage.content.properties 
                                      or 'expiration' not in amqpMessage.content.properties['headers']) 
                                  else amqpMessage.content.properties['headers']['expiration'],
                           r.request.params['source_addr'],
                           r.request.params['destination_addr'],
                           r.request.params['short_message']
                           ))

        # Cancel any mapped rejectTimer to this message (in case this message was rejected in the past)
        self.clearRejectTimer(msgid)

        self.log.debug("ACKing amqpMessage [%s] having routing_key [%s]", msgid, amqpMessage.routing_key)
        # ACK the message in queue, this will remove it from the queue
        yield self.ackMessage(amqpMessage)
        
        # Redis client is connected ?
        if self.redisClient is not None:
            # Check for HTTP DLR request from redis 'dlr' key
            # If there's a pending delivery receipt request then serve it
            # back by publishing a DLRContentForHttpapi to the messaging exchange
            pickledDlr = None
            pickledSmppsMap = None
            pickledDlr = yield self.redisClient.get("dlr:%s" % msgid)
            if pickledDlr is None:
                pickledSmppsMap = yield self.redisClient.get("smppsmap:%s" % msgid)

            if pickledDlr is not None:
                self.log.debug('There is a HTTP DLR request for msgid[%s] ...' % (msgid))

                dlr = pickle.loads(pickledDlr)
                dlr_url = dlr['url']
                dlr_level = dlr['level']
                dlr_method = dlr['method']
                dlr_expiry = dlr['expiry']

                if dlr_level in [1, 3]:
                    self.log.debug('Got DLR information for msgid[%s], url:%s, level:%s' % (msgid, 
                                                                                            dlr_url, 
                                                                                            dlr_level))
                    content = DLRContentForHttpapi(str(r.response.status), 
                                         msgid, 
                                         dlr_url, 
                                         # The dlr_url in DLRContentForHttpapi indicates the level
                                         # of the actual delivery receipt (1) and not the requested
                                         # one (maybe 1 or 3)
                                         dlr_level = 1, 
                                         method = dlr_method)
                    routing_key = 'dlr_thrower.http'
                    self.log.debug("Publishing DLRContentForHttpapi[%s] with routing_key[%s]" % (msgid, routing_key))
                    yield self.amqpBroker.publish(exchange='messaging', 
                                                  routing_key=routing_key, 
                                                  content=content)
                    
                    # DLR request is removed if:
                    # - If level 1 is requested (SMSC level only)
                    # - SubmitSmResp returned an error (no more delivery will be tracked)
                    #
                    # When level 3 is requested, the DLR will be removed when 
                    # receiving a deliver_sm (terminal receipt)
                    if dlr_level == 1 or r.response.status != CommandStatus.ESME_ROK:
                        self.log.debug('Removing DLR request for msgid[%s]' % msgid)
                        yield self.redisClient.delete("dlr:%s" % msgid)
                else:
                    self.log.debug('Terminal level receipt is requested, will not send any DLR receipt at this level.')
                
                if dlr_level in [2, 3]:
                    # Map received submit_sm_resp's message_id to the msg for later rceipt handling
                    self.log.debug('Mapping smpp msgid: %s to queue msgid: %s, expiring in %s' % (
                                    r.response.params['message_id'],
                                    msgid, 
                                    dlr_expiry
                                    )
                                   )
                    hashKey = "queue-msgid:%s" % r.response.params['message_id']
                    hashValues = {'msgid': msgid, 
                                  'connector_type': 'httpapi',}
                    self.redisClient.set(hashKey, pickle.dumps(hashValues, self.pickleProtocol)).addCallback(
                        self.setKeyExpiry, hashKey, dlr_expiry)
            elif pickledSmppsMap is not None:
                self.log.debug('There is a SMPPs mapping for msgid[%s] ...' % (msgid))

                smpps_map = pickle.loads(pickledSmppsMap)
                system_id = smpps_map['system_id']
                source_addr = smpps_map['source_addr']
                destination_addr = smpps_map['destination_addr']
                registered_delivery = smpps_map['registered_delivery']
                smpps_map_expiry = smpps_map['expiry']

                # Do we need to forward the receipt to the original sender ?
                if ((r.response.status == CommandStatus.ESME_ROK and 
                        str(registered_delivery.receipt) in ['SMSC_DELIVERY_RECEIPT_REQUESTED', 
                                                             'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE'])
                    or (r.response.status != CommandStatus.ESME_ROK and 
                        str(registered_delivery.receipt) == 'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE')):
                    self.log.debug('Got DLR information for msgid[%s], registered_deliver%s, system_id:%s' % (msgid, 
                                                                                                       registered_delivery,
                                                                                                       system_id))
                    
                    content = DLRContentForSmpps(str(r.response.status), 
                                                 msgid, 
                                                 system_id,
                                                 source_addr,
                                                 destination_addr)

                    routing_key = 'dlr_thrower.smpps'
                    self.log.debug("Publishing DLRContentForSmpps[%s] with routing_key[%s]" % (msgid, routing_key))
                    yield self.amqpBroker.publish(exchange='messaging', 
                                                  routing_key=routing_key, 
                                                  content=content)

                    # Map received submit_sm_resp's message_id to the msg for later rceipt handling
                    self.log.debug('Mapping smpp msgid: %s to queue msgid: %s, expiring in %s' % (
                                    r.response.params['message_id'],
                                    msgid, 
                                    smpps_map_expiry
                                    )
                                   )
                    hashKey = "queue-msgid:%s" % r.response.params['message_id']
                    hashValues = {'msgid': msgid, 
                                  'connector_type': 'smpps',}
                    self.redisClient.set(hashKey, pickle.dumps(hashValues, self.pickleProtocol)).addCallback(
                        self.setKeyExpiry, hashKey, smpps_map_expiry)
        else:
            self.log.warn('No valid RC were found while checking msg[%s] !' % msgid)
        
        # Bill will be charged by bill_request.submit_sm_resp.UID queue consumer
        if total_bill_amount > 0:
            pubQueueName = 'bill_request.submit_sm_resp.%s' % submit_sm_resp_bill.user.uid
            content = SubmitSmRespBillContent(submit_sm_resp_bill.bid, submit_sm_resp_bill.user.uid, total_bill_amount)
            self.log.debug("Requesting a SubmitSmRespBillContent from a bill [bid:%s] with routing_key[%s]: %s" % 
                           (submit_sm_resp_bill.bid, pubQueueName, total_bill_amount))
            yield self.amqpBroker.publish(exchange='billing', 
                                          routing_key=pubQueueName, 
                                          content=content)
        
        if self.config.publish_submit_sm_resp:
            # Send back submit_sm_resp to submit.sm.resp.CID queue
            # There's no actual listeners on this queue, it can be used to 
            # track submit_sm_resp messages from a 3rd party app
            content = SubmitSmRespContent(r.response, msgid, pickleProtocol = self.pickleProtocol)
            self.log.debug("Sending back SubmitSmRespContent[%s] with routing_key[%s]" % 
                           (msgid, amqpMessage.content.properties['reply-to']))
            yield self.amqpBroker.publish(exchange='messaging', 
                                          routing_key=amqpMessage.content.properties['reply-to'], 
                                          content=content)

    def submit_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) == None:
            #@todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside submit_sm_callback
            # - the qosTimer has been cancelled (self.clearQosTimer())
            self.log.error("Error in submit_sm_errback: %s" % error.getErrorMessage())
       
    @defer.inlineCallbacks     
    def concatDeliverSMs(self, HSetReturn, splitMethod, total_segments, msg_ref_num, segment_seqnum):
        hashKey = "longDeliverSm:%s" % (msg_ref_num)
        if HSetReturn != 1:
            self.log.warn('Error (%s) when trying to set hashKey %s' % (HSetReturn, hashKey))
            return

        # @TODO: longDeliverSm part expiry must be configurable
        yield self.redisClient.expire(hashKey, 300)
        
        # This is the last part
        if segment_seqnum == total_segments:
            hvals = yield self.redisClient.hvals(hashKey)
            if len(hvals) != total_segments:
                self.log.warn('Received the last part (msg_ref_num:%s) and did not find all parts in redis, data lost !' % msg_ref_num)
                return
            
            # Get PDUs
            pdus = {}
            for pickledValue in hvals:
                value = pickle.loads(pickledValue)
                
                pdus[value['segment_seqnum']] = value['pdu']

            # Build short_message
            short_message = ''
            for i in range(total_segments):
                if splitMethod == 'sar':
                    short_message += pdus[i+1].params['short_message']
                else:
                    short_message += pdus[i+1].params['short_message'][6:]
                
            # Build the final pdu and return it back to deliver_sm_event
            pdu = pdus[1] # Take the first part as a base of work
            # 1. Remove message splitting information from pdu
            if splitMethod == 'sar':
                del(pdu.params['sar_segment_seqnum'])
                del(pdu.params['sar_total_segments'])
                del(pdu.params['sar_msg_ref_num'])
            else:
                pdu.params['esm_class'] = None
            # 2. Set the new short_message
            pdu.params['short_message'] = short_message
            yield self.deliver_sm_event(smpp = None, pdu = pdu, concatenated = True)
    
    @defer.inlineCallbacks
    def deliver_sm_event(self, smpp, pdu, concatenated = False):
        """This event is called whenever a deliver_sm pdu is received through a SMPPc
        It will hand the pdu to the router or a dlr thrower (depending if its a DLR or not).
        """

        pdu.dlr =  self.SMPPOperationFactory.isDeliveryReceipt(pdu)
        content = DeliverSmContent(pdu, 
                                   self.SMPPClientFactory.config.id, 
                                   pickleProtocol = self.pickleProtocol,
                                   concatenated = concatenated)
        msgid = content.properties['message-id']
        
        if pdu.dlr is None:
            # We have a SMS-MO

            # UDH is set ?
            UDHI_INDICATOR_SET = False
            if hasattr(pdu.params['esm_class'], 'gsmFeatures'):
                for gsmFeature in pdu.params['esm_class'].gsmFeatures:
                    if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                        UDHI_INDICATOR_SET = True
                        break

            splitMethod = None
            # Is it a part of a long message ?
            if 'sar_msg_ref_num' in pdu.params:
                splitMethod = 'sar'
                total_segments = pdu.params['sar_total_segments']
                segment_seqnum = pdu.params['sar_segment_seqnum']
                msg_ref_num = pdu.params['sar_msg_ref_num']
                self.log.debug('Received a part of SMS-MO [queue-msgid:%s] using SAR options: total_segments=%s, segmen_seqnum=%s, msg_ref_num=%s' % (msgid, total_segments, segment_seqnum, msg_ref_num))
            elif UDHI_INDICATOR_SET and pdu.params['short_message'][:3] == '\x05\x00\x03':
                splitMethod = 'udh'
                total_segments = struct.unpack('!B', pdu.params['short_message'][4])[0]
                segment_seqnum = struct.unpack('!B', pdu.params['short_message'][5])[0]
                msg_ref_num = struct.unpack('!B', pdu.params['short_message'][3])[0]
                self.log.debug('Received a part of SMS-MO [queue-msgid:%s] using UDH options: total_segments=%s, segmen_seqnum=%s, msg_ref_num=%s' % (msgid, total_segments, segment_seqnum, msg_ref_num))
            
            if splitMethod is None:
                # It's a simple short message or a part of a concatenated message
                routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                self.log.debug("Publishing DeliverSmContent[%s] with routing_key[%s]" % (msgid, routing_key))
                yield self.amqpBroker.publish(exchange='messaging', routing_key=routing_key, content=content)
                
                self.log.info("SMS-MO [cid:%s] [queue-msgid:%s] [status:%s] [prio:%s] [validity:%s] [from:%s] [to:%s] [content:%s]" % 
                          (
                           self.SMPPClientFactory.config.id,
                           msgid,
                           pdu.status,
                           pdu.params['priority_flag'],
                           pdu.params['validity_period'],
                           pdu.params['source_addr'],
                           pdu.params['destination_addr'],
                           pdu.params['short_message']
                           ))
            else:
                # Long message part received
                if self.redisClient is None:
                    self.warn('No valid RC were found while receiving a part of a long DeliverSm [queue-msgid:%s], MESSAGE IS LOST !' % msgid)
                
                # Save it to redis
                hashKey = "longDeliverSm:%s" % (msg_ref_num)
                hashValues = {'pdu': pdu, 
                              'total_segments':total_segments, 
                              'msg_ref_num':msg_ref_num, 
                              'segment_seqnum':segment_seqnum}
                self.redisClient.hset(hashKey, segment_seqnum, pickle.dumps(hashValues, 
                                                                           self.pickleProtocol
                                                                           )
                                      ).addCallback(self.concatDeliverSMs, 
                                                    splitMethod, 
                                                    total_segments, 
                                                    msg_ref_num, 
                                                    segment_seqnum)
                
                self.log.info("DeliverSmContent[%s] is a part of a long message of %s parts, will be sent to queue after concatenation." % (msgid, total_segments))

                # Flag it as "will_be_concatenated" and publish it to router
                routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                self.log.debug("Publishing DeliverSmContent[%s](flagged:wbc) with routing_key[%s]" % (msgid, routing_key))
                content.properties['headers']['will_be_concatenated'] = True
                yield self.amqpBroker.publish(exchange='messaging', routing_key=routing_key, content=content)
        else:
            # This is a DLR !
            # Check for DLR request
            if self.redisClient is not None:
                q = yield self.redisClient.get("queue-msgid:%s" % pdu.dlr['id'])
                submit_sm_queue_id = None
                connector_type = None
                if q is not None:
                    q = pickle.loads(q)
                    submit_sm_queue_id = q['msgid']
                    connector_type = q['connector_type']


                if submit_sm_queue_id is not None and connector_type == 'httpapi':
                    pickledDlr = yield self.redisClient.get("dlr:%s" % submit_sm_queue_id)
                    
                    if pickledDlr is not None:
                        dlr = pickle.loads(pickledDlr)
                        dlr_url = dlr['url']
                        dlr_level = dlr['level']
                        dlr_method = dlr['method']

                        if dlr_level in [2, 3]:
                            self.log.debug('Got DLR information for msgid[%s], url:%s, level:%s' % 
                                           (submit_sm_queue_id, dlr_url, dlr_level))
                            content = DLRContentForHttpapi(pdu.dlr['stat'], 
                                                 submit_sm_queue_id, 
                                                 dlr_url, 
                                                 # The dlr_url in DLRContentForHttpapi indicates the level
                                                 # of the actual delivery receipt (2) and not the 
                                                 # requested one (maybe 2 or 3)
                                                 dlr_level = 2, 
                                                 id_smsc = pdu.dlr['id'], 
                                                 sub = pdu.dlr['sub'], 
                                                 dlvrd = pdu.dlr['dlvrd'], 
                                                 subdate = pdu.dlr['sdate'], 
                                                 donedate = pdu.dlr['ddate'], 
                                                 err = pdu.dlr['err'], 
                                                 text = pdu.dlr['text'],
                                                 method = dlr_method)
                            routing_key = 'dlr_thrower.http'
                            self.log.debug("Publishing DLRContentForHttpapi[%s] with routing_key[%s]" % 
                                           (submit_sm_queue_id, routing_key))
                            yield self.amqpBroker.publish(exchange='messaging', 
                                                          routing_key=routing_key, 
                                                          content=content)
                            
                            self.log.debug('Removing DLR request for msgid[%s]' % submit_sm_queue_id)
                            yield self.redisClient.delete('dlr:%s' % submit_sm_queue_id)
                        else:
                            self.log.debug('SMS-C receipt is requested, will not send any DLR receipt at this level.')
                    else:
                        self.log.warn('Got invalid DLR information for msgid[%s], url:%s, level:%s' % 
                                      (submit_sm_queue_id, dlr_url, dlr_level))
                elif submit_sm_queue_id is not None and connector_type == 'smpps':
                    pickledSmppsMap = yield self.redisClient.get("smppsmap:%s" % submit_sm_queue_id)
                    
                    if pickledSmppsMap is not None:
                        smpps_map = pickle.loads(pickledSmppsMap)
                        system_id = smpps_map['system_id']
                        source_addr = smpps_map['source_addr']
                        destination_addr = smpps_map['destination_addr']
                        registered_delivery = smpps_map['registered_delivery']
                        smpps_map_expiry = smpps_map['expiry']

                        success_states = ['ACCEPTD', 'DELIVRD']
                        final_states = ['DELIVRD', 'EXPIRED', 'DELETED', 'UNDELIV', 'REJECTD']
                        # Do we need to forward the receipt to the original sender ?
                        if ((pdu.dlr['stat'] in success_states and 
                                str(registered_delivery.receipt) in ['SMSC_DELIVERY_RECEIPT_REQUESTED', 
                                                                     'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE'])
                            or (pdu.dlr['stat'] not in success_states and 
                                str(registered_delivery.receipt) == 'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE')):

                            self.log.debug('Got DLR information for msgid[%s], registered_deliver%s, system_id:%s' % (submit_sm_queue_id, 
                                                                                                                      registered_delivery,
                                                                                                                      system_id))
                            content = DLRContentForSmpps(pdu.dlr['stat'], 
                                                         submit_sm_queue_id, 
                                                         system_id,
                                                         source_addr,
                                                         destination_addr)

                            routing_key = 'dlr_thrower.smpps'
                            self.log.debug("Publishing DLRContentForSmpps[%s] with routing_key[%s]" % (submit_sm_queue_id, routing_key))
                            yield self.amqpBroker.publish(exchange='messaging', 
                                                          routing_key=routing_key, 
                                                          content=content)

                            if pdu.dlr['stat'] in final_states:
                                self.log.debug('Removing SMPPs map for msgid[%s]' % submit_sm_queue_id)
                                yield self.redisClient.delete('smppsmap:%s' % submit_sm_queue_id)
                else:
                    self.log.warn('Got a DLR for an unknown message id: %s' % pdu.dlr['id'])
            else:
                self.log.warn('DLR for msgid[%s] is not checked, no valid RC were found' % msgid)

            self.log.info("DLR [cid:%s] [smpp-msgid:%s] [status:%s] [submit date:%s] [done date:%s] [submitted/delivered messages:%s/%s] [err:%s] [content:%s]" % 
                      (
                       self.SMPPClientFactory.config.id,
                       pdu.dlr['id'],
                       pdu.dlr['stat'],
                       pdu.dlr['sdate'],
                       pdu.dlr['ddate'],
                       pdu.dlr['sub'],
                       pdu.dlr['dlvrd'],
                       pdu.dlr['err'],
                       pdu.dlr['text'],
                       ))
Esempio n. 18
0
class Rate(Resource):
    isleaf = True

    def __init__(self, HTTPApiConfig, RouterPB, stats, log,
                 interceptorpb_client):
        Resource.__init__(self)

        self.RouterPB = RouterPB
        self.stats = stats
        self.log = log
        self.interceptorpb_client = interceptorpb_client

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(
            long_content_max_parts=HTTPApiConfig.long_content_max_parts,
            long_content_split=HTTPApiConfig.long_content_split)

    @defer.inlineCallbacks
    def route_routable(self, request):
        try:
            # Authentication
            user = self.RouterPB.authenticateUser(
                username=request.args['username'][0],
                password=request.args['password'][0])
            if user is None:
                self.stats.inc('auth_error_count')

                self.log.debug(
                    "Authentication failure for username:%s and password:%s",
                    request.args['username'][0], request.args['password'][0])
                self.log.error("Authentication failure for username:%s",
                               request.args['username'][0])
                raise AuthenticationError(
                    'Authentication failure for username:%s' %
                    request.args['username'][0])

            # Update CnxStatus
            user.getCnxStatus().httpapi['connects_count'] += 1
            user.getCnxStatus().httpapi['rate_request_count'] += 1
            user.getCnxStatus().httpapi['last_activity_at'] = datetime.now()

            # Build SubmitSmPDU
            SubmitSmPDU = self.opFactory.SubmitSM(
                source_add=None
                if 'from' not in request.args else request.args['from'][0],
                destination_addr=request.args['to'][0],
                short_message=request.args['content'][0],
                data_coding=int(request.args['coding'][0]),
            )
            self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU)

            # Make Credential validation
            v = HttpAPICredentialValidator('Rate',
                                           user,
                                           request,
                                           submit_sm=SubmitSmPDU)
            v.validate()

            # Update SubmitSmPDU by default values from user MtMessagingCredential
            SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU)

            # Prepare for interception than routing
            routable = RoutableSubmitSm(SubmitSmPDU, user)
            self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable,
                           SubmitSmPDU)

            # Should we tag the routable ?
            tags = []
            if 'tags' in request.args:
                tags = request.args['tags'][0].split(',')
                for tag in tags:
                    routable.addTag(tag)
                    self.log.debug('Tagged routable %s: +%s', routable, tag)

            # Intercept
            interceptor = self.RouterPB.getMTInterceptionTable(
            ).getInterceptorFor(routable)
            if interceptor is not None:
                self.log.debug(
                    "RouterPB selected %s interceptor for this SubmitSmPDU",
                    interceptor)
                if self.interceptorpb_client is None:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not set !")
                    raise InterceptorNotSetError('InterceptorPB not set !')
                if not self.interceptorpb_client.isConnected:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not connected !")
                    raise InterceptorNotConnectedError(
                        'InterceptorPB not connected !')

                script = interceptor.getScript()
                self.log.debug("Interceptor script loaded: %s", script)

                # Run !
                r = yield self.interceptorpb_client.run_script(
                    script, routable)
                if isinstance(r, dict) and r['http_status'] != 200:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Interceptor script returned %s http_status error.',
                        r['http_status'])
                    raise InterceptorRunError(
                        code=r['http_status'],
                        message='Interception specific error code %s' %
                        r['http_status'])
                elif isinstance(r, str):
                    self.stats.inc('interceptor_count')
                    routable = pickle.loads(r)
                else:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got the following return: %s',
                        r)
                    raise InterceptorRunError(
                        message=
                        'Failed running interception script, check log for details'
                    )

            # Routing
            route = self.RouterPB.getMTRoutingTable().getRouteFor(routable)
            if route is None:
                self.log.error(
                    "No route matched from user %s for SubmitSmPDU: %s", user,
                    SubmitSmPDU)
                raise RouteNotFoundError("No route found")

            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU", route)

            # Get number of PDUs to be sent (for billing purpose)
            _pdu = SubmitSmPDU
            submit_sm_count = 1
            while hasattr(_pdu, 'nextPdu'):
                _pdu = _pdu.nextPdu
                submit_sm_count += 1

            # Get the bill
            bill = route.getBillFor(user)

            response = {
                'return': {
                    'unit_rate': bill.getTotalAmounts(),
                    'submit_sm_count': submit_sm_count
                },
                'status': 200
            }
        except Exception, e:
            self.log.error("Error: %s", e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}
        finally:
Esempio n. 19
0
class DLRThrower(Thrower):
    name = 'DLRThrower'

    def __init__(self, config):
        self.log_category = "jasmin-dlr-thrower"
        self.exchangeName = 'messaging'
        self.consumerTag = 'DLRThrower'
        self.routingKey = 'dlr_thrower.*'
        self.queueName = 'dlr_thrower'
        self.callback = self.dlr_throwing_callback
        self.opFactory = SMPPOperationFactory()

        Thrower.__init__(self, config)

    @defer.inlineCallbacks
    def http_dlr_callback(self, message):
        msgid = message.content.properties['message-id']
        url = message.content.properties['headers']['url']
        method = message.content.properties['headers']['method']
        level = message.content.properties['headers']['level']
        self.log.debug('Got one message (msgid:%s) to throw', msgid)

        # If any, clear requeuing timer
        self.clearRequeueTimer(msgid)

        # Build mandatory arguments
        args = {
            'id':
            msgid,
            'level':
            level,
            'message_status':
            message.content.properties['headers']['message_status']
        }

        # Level 2 extra args
        if level in [2, 3]:
            args['id_smsc'] = message.content.properties['headers']['id_smsc']
            args['sub'] = message.content.properties['headers']['sub']
            args['dlvrd'] = message.content.properties['headers']['dlvrd']
            args['subdate'] = message.content.properties['headers']['subdate']
            args['donedate'] = message.content.properties['headers'][
                'donedate']
            args['err'] = message.content.properties['headers']['err']
            args['text'] = message.content.properties['headers']['text']

        try:
            # Throw the message to http endpoint
            encodedArgs = urllib.urlencode(args)
            postdata = None
            baseurl = url
            if method == 'GET':
                baseurl += '?%s' % encodedArgs
            else:
                postdata = encodedArgs

            self.log.debug('Calling %s with args %s using %s method.', baseurl,
                           encodedArgs, method)
            content = yield getPage(baseurl,
                                    method=method,
                                    postdata=postdata,
                                    timeout=self.config.timeout,
                                    agent='Jasmin gateway/1.0 %s' % self.name,
                                    headers={
                                        'Content-Type':
                                        'application/x-www-form-urlencoded',
                                        'Accept': 'text/plain'
                                    })
            self.log.info('Throwed DLR [msgid:%s] to %s.', msgid, baseurl)

            self.log.debug('Destination end replied to message [msgid:%s]: %r',
                           msgid, content)
            # Check for acknowledgement
            if content.strip() != 'ACK/Jasmin':
                raise MessageAcknowledgementError(
                    'Destination end did not acknowledge receipt of the DLR message.'
                )

            # Everything is okay ? then:
            yield self.ackMessage(message)
        except Exception as e:
            self.log.error('Throwing HTTP/DLR [msgid:%s] to (%s): %r.', msgid,
                           baseurl, e)

            # List of errors after which, no further retrying shall be made
            noRetryErrors = ['404 Not Found']

            # Requeue message for later retry
            if (str(e) not in noRetryErrors
                    and self.getThrowingRetrials(message) <=
                    self.config.max_retries):
                self.log.debug('Message try-count is %s [msgid:%s]: requeuing',
                               self.getThrowingRetrials(message), msgid)
                yield self.rejectAndRequeueMessage(message)
            elif str(e) in noRetryErrors:
                self.log.warn(
                    'Message is no more processed after receiving "%s" error',
                    str(e))
                yield self.rejectMessage(message)
            else:
                self.log.warn(
                    'Message try-count is %s [msgid:%s]: purged from queue',
                    self.getThrowingRetrials(message), msgid)
                yield self.rejectMessage(message)

    @defer.inlineCallbacks
    def smpp_dlr_callback(self, message):
        msgid = message.content.properties['message-id']
        system_id = message.content.properties['headers']['system_id']
        message_status = message.content.properties['headers'][
            'message_status']
        source_addr = '%s' % message.content.properties['headers'][
            'source_addr']
        destination_addr = '%s' % message.content.properties['headers'][
            'destination_addr']
        sub_date = message.content.properties['headers']['sub_date']
        source_addr_ton = message.content.properties['headers'][
            'source_addr_ton']
        source_addr_npi = message.content.properties['headers'][
            'source_addr_npi']
        dest_addr_ton = message.content.properties['headers']['dest_addr_ton']
        dest_addr_npi = message.content.properties['headers']['dest_addr_npi']
        self.log.debug('Got one message (msgid:%s) to throw', msgid)

        # If any, clear requeuing timer
        self.clearRequeueTimer(msgid)

        try:
            if self.smpps is None or self.smpps_access is None:
                raise SmppsNotSetError()

            # Get bound connections (or systemids)
            if self.smpps_access == 'direct':
                bound_systemdids = self.smpps.bound_connections
            else:
                bound_systemdids = yield self.smpps.list_bound_systemids()

            if system_id not in bound_systemdids:
                raise SystemIdNotBound(system_id)

            # Build the Receipt PDU (data_sm)
            pdu = self.opFactory.getReceipt(dlr_pdu=self.config.dlr_pdu,
                                            msgid=msgid,
                                            source_addr=source_addr,
                                            destination_addr=destination_addr,
                                            message_status=message_status,
                                            sub_date=sub_date,
                                            source_addr_ton=source_addr_ton,
                                            source_addr_npi=source_addr_npi,
                                            dest_addr_ton=dest_addr_ton,
                                            dest_addr_npi=dest_addr_npi)

            # Pick a deliverer and sendRequest
            if self.smpps_access == 'direct':
                deliverer = bound_systemdids[
                    system_id].getNextBindingForDelivery()

                if deliverer is None:
                    raise NoDelivererForSystemId(system_id)

                yield deliverer.sendRequest(
                    pdu,
                    deliverer.config().responseTimerSecs)
            else:
                r = yield self.smpps.deliverer_send_request(system_id, pdu)
                if not r:
                    raise DeliveringFailed(
                        'Delivering failed, check %s smpps logs for more details'
                        % system_id)
        except Exception as e:
            self.log.error('Throwing SMPP/DLR [msgid:%s] to (%s): %r.', msgid,
                           system_id, e)

            # List of exceptions after which, no further retrying shall be made
            noRetryExceptions = [SmppsNotSetError]

            retry = True
            for noRetryException in noRetryExceptions:
                if isinstance(e, noRetryException):
                    retry = False
                    break

            # Requeue message for later retry
            if retry and self.getThrowingRetrials(
                    message) <= self.config.max_retries:
                self.log.debug('Message try-count is %s [msgid:%s]: requeuing',
                               self.getThrowingRetrials(message), msgid)
                yield self.rejectAndRequeueMessage(message)
            elif retry and self.getThrowingRetrials(
                    message) > self.config.max_retries:
                self.log.warn(
                    'Message is no more processed after receiving "%s" error',
                    str(e))
                yield self.rejectMessage(message)
            else:
                self.log.warn(
                    'Message try-count is %s [msgid:%s]: purged from queue',
                    self.getThrowingRetrials(message), msgid)
                yield self.rejectMessage(message)
        else:
            # Everything is okay ? then:
            yield self.ackMessage(message)

    @defer.inlineCallbacks
    def dlr_throwing_callback(self, message):
        Thrower.throwing_callback(self, message)

        if message.routing_key == 'dlr_thrower.http':
            yield self.http_dlr_callback(message)
        elif message.routing_key == 'dlr_thrower.smpps':
            yield self.smpp_dlr_callback(message)
        else:
            self.log.error('Unknown routing_key in dlr_throwing_callback: %s',
                           message.routing_key)
            yield self.rejectMessage(message)
Esempio n. 20
0
class OperationsTest(TestCase):
    source_addr         = '20203060'
    destination_addr    = '98700177'
    latin1_sm           = '6162636465666768696a6b6c6d6e6f707172737475767778797a'
    latin1_long_sm      = '6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e6162636465666768696a6b6c6d6e6f707172737475767778797a2e'

    def setUp(self):
        self.opFactory = SMPPOperationFactory(SMPPClientConfig(id='test-id'))
        
    def buildSubmitSmTest(self, sm):
        """
        Build a SubmitSm pdu and test if:
         - command_id is correct
         - command_status is ESME_ROCK (default value)
         - destination_addr is the same as self.destination_addr
         - source_addr is the same as self.source_addr
        """

        pdu = self.opFactory.SubmitSM(
            source_addr=self.source_addr,
            destination_addr=self.destination_addr,
            short_message=sm,
        )

        self.assertEquals(pdu.id, CommandId.submit_sm)
        self.assertEquals(pdu.status, CommandStatus.ESME_ROK)
        self.assertEquals(pdu.params['destination_addr'], self.destination_addr)
        self.assertEquals(pdu.params['source_addr'], self.source_addr)
        
        return pdu

    def test_encode_latin1(self):
        """
        Test that a latin1 short message text remain the same after it's getting
        encoded in a PDU object.
        """

        sm = binascii.a2b_hex(self.latin1_sm)
        pdu = self.buildSubmitSmTest(sm)        

        # SM shall not be altered since it is not sliced (not too long)
        self.assertEquals(pdu.params['short_message'], sm)

    def test_encode_latin1_long(self):
        """
        Test that a latin1 short message long text gets successfully sliced into
        multiple PDUs (parts)
        """

        sm = binascii.a2b_hex(self.latin1_long_sm)
        pdu = self.buildSubmitSmTest(sm)
        
        # The first PDU shall have a next one
        self.assertTrue(isinstance(pdu.nextPdu, SubmitSM))
        # These UDH parameters shall be present in all PDUs
        self.assertTrue(pdu.params['sar_msg_ref_num'] > 0)
        self.assertTrue(pdu.params['sar_total_segments'] > 0)
        self.assertTrue(pdu.params['sar_segment_seqnum'] > 0)
        
        # Iterating through sliced PDUs
        partedSmPdu = pdu
        assembledSm= ''
        lastSeqNum = 0
        while True:
            assembledSm += partedSmPdu.params['short_message']
            
            self.assertTrue(partedSmPdu.params['sar_msg_ref_num'] == pdu.params['sar_msg_ref_num'])
            self.assertTrue(partedSmPdu.params['sar_total_segments'] == pdu.params['sar_total_segments'])
            self.assertTrue(partedSmPdu.params['sar_segment_seqnum'] > lastSeqNum)
            lastSeqNum = partedSmPdu.params['sar_segment_seqnum']

            try:
                partedSmPdu = partedSmPdu.nextPdu
            except AttributeError:
                break
            
        # Assembled SM shall be equal to the original SM
        self.assertEquals(assembledSm, sm)
        
        # The last seqNum shall be equal to total segments
        self.assertEquals(lastSeqNum, pdu.params['sar_total_segments'])
        
    def test_is_delivery(self):
        pdu = DeliverSM(
            source_addr='1234',
            destination_addr='4567',
            short_message='id:1891273321 sub:001 dlvrd:001 submit date:1305050826 done date:1305050826 stat:DELIVRD err:000 text:DLVRD TO MOBILE',
        )
        
        isDlr = self.opFactory.isDeliveryReceipt(pdu)
        self.assertTrue(isDlr is not None)
        self.assertEquals(isDlr['id'], '1891273321')
        self.assertEquals(isDlr['sub'], '001')
        self.assertEquals(isDlr['dlvrd'], '001')
        self.assertEquals(isDlr['sdate'], '1305050826')
        self.assertEquals(isDlr['ddate'], '1305050826')
        self.assertEquals(isDlr['stat'], 'DELIVRD')
        self.assertEquals(isDlr['err'], '000')
        self.assertEquals(isDlr['text'], 'DLVRD TO MOBILE')
Esempio n. 21
0
class DLRThrower(Thrower):
    name = 'DLRThrower'

    def __init__(self, config):
        self.log_category = "jasmin-dlr-thrower"
        self.exchangeName = 'messaging'
        self.consumerTag = 'DLRThrower'
        self.routingKey = 'dlr_thrower.*'
        self.queueName = 'dlr_thrower'
        self.callback = self.dlr_throwing_callback
        self.opFactory = SMPPOperationFactory()

        Thrower.__init__(self, config)

    @defer.inlineCallbacks
    def http_dlr_callback(self, message):
        msgid = message.content.properties['message-id']
        url = message.content.properties['headers']['url']
        method = message.content.properties['headers']['method']
        level = message.content.properties['headers']['level']
        self.log.debug('Got one message (msgid:%s) to throw', msgid)

        # If any, clear requeuing timer
        self.clearRequeueTimer(msgid)

        # Build mandatory arguments
        args = {'id': msgid, 'level': level,
                'message_status': message.content.properties['headers']['message_status']}

        # Level 2 extra args
        if level in [2, 3]:
            args['id_smsc'] = message.content.properties['headers']['id_smsc']
            args['sub'] = message.content.properties['headers']['sub']
            args['dlvrd'] = message.content.properties['headers']['dlvrd']
            args['subdate'] = message.content.properties['headers']['subdate']
            args['donedate'] = message.content.properties['headers']['donedate']
            args['err'] = message.content.properties['headers']['err']
            args['text'] = message.content.properties['headers']['text']

        try:
            # Throw the message to http endpoint
            encodedArgs = urllib.urlencode(args)
            postdata = None
            baseurl = url
            if method == 'GET':
                baseurl += '?%s' % encodedArgs
            else:
                postdata = encodedArgs

            self.log.debug('Calling %s with args %s using %s method.', baseurl, encodedArgs, method)
            if '127.0.0.1' not in baseurl:
                content = yield getPage(
                    baseurl,
                    method=method,
                    postdata=postdata,
                    timeout=self.config.timeout,
                    agent='Jasmin gateway/1.0 %s' % self.name,
                    headers={'Content-Type': 'application/x-www-form-urlencoded',
                             'Accept': 'text/plain'})
            else:
                content = 'ACK/Jasmin'
            self.log.info('Throwed DLR [msgid:%s] to %s.', msgid, baseurl)

            self.log.debug('Destination end replied to message [msgid:%s]: %r', msgid, content)
            # Check for acknowledgement
            if content.strip() != 'ACK/Jasmin':
                raise MessageAcknowledgementError(
                    'Destination end did not acknowledge receipt of the DLR message.')

            # Everything is okay ? then:
            yield self.ackMessage(message)
        except Exception as e:
            self.log.error('Throwing HTTP/DLR [msgid:%s] to (%s): %r.', msgid, baseurl, e)

            # List of errors after which, no further retrying shall be made
            noRetryErrors = ['404 Not Found']

            # Requeue message for later retry
            if (str(e) not in noRetryErrors
                and self.getThrowingRetrials(message) <= self.config.max_retries):
                self.log.debug('Message try-count is %s [msgid:%s]: requeuing',
                               self.getThrowingRetrials(message), msgid)
                yield self.rejectAndRequeueMessage(message)
            elif str(e) in noRetryErrors:
                self.log.warn('Message is no more processed after receiving "%s" error', str(e))
                yield self.rejectMessage(message)
            else:
                self.log.warn('Message try-count is %s [msgid:%s]: purged from queue',
                              self.getThrowingRetrials(message), msgid)
                yield self.rejectMessage(message)

    @defer.inlineCallbacks
    def smpp_dlr_callback(self, message):
        msgid = message.content.properties['message-id']
        system_id = message.content.properties['headers']['system_id']
        message_status = message.content.properties['headers']['message_status']
        source_addr = '%s' % message.content.properties['headers']['source_addr']
        destination_addr = '%s' % message.content.properties['headers']['destination_addr']
        sub_date = message.content.properties['headers']['sub_date']
        source_addr_ton = message.content.properties['headers']['source_addr_ton']
        source_addr_npi = message.content.properties['headers']['source_addr_npi']
        dest_addr_ton = message.content.properties['headers']['dest_addr_ton']
        dest_addr_npi = message.content.properties['headers']['dest_addr_npi']
        self.log.debug('Got one message (msgid:%s) to throw', msgid)

        # If any, clear requeuing timer
        self.clearRequeueTimer(msgid)

        try:
            if self.smpps is None or self.smpps_access is None:
                raise SmppsNotSetError()

            # Get bound connections (or systemids)
            if self.smpps_access == 'direct':
                bound_systemdids = self.smpps.bound_connections
            else:
                bound_systemdids = yield self.smpps.list_bound_systemids()

            if system_id not in bound_systemdids:
                raise SystemIdNotBound(system_id)

            # Build the Receipt PDU (data_sm)
            pdu = self.opFactory.getReceipt(dlr_pdu=self.config.dlr_pdu,
                                            msgid=msgid,
                                            source_addr=source_addr,
                                            destination_addr=destination_addr,
                                            message_status=message_status,
                                            sub_date=sub_date,
                                            source_addr_ton=source_addr_ton,
                                            source_addr_npi=source_addr_npi,
                                            dest_addr_ton=dest_addr_ton,
                                            dest_addr_npi=dest_addr_npi)

            # Pick a deliverer and sendRequest
            if self.smpps_access == 'direct':
                deliverer = bound_systemdids[system_id].getNextBindingForDelivery()

                if deliverer is None:
                    raise NoDelivererForSystemId(system_id)

                yield deliverer.sendRequest(pdu, deliverer.config().responseTimerSecs)
            else:
                r = yield self.smpps.deliverer_send_request(system_id, pdu)
                if not r:
                    raise DeliveringFailed('Delivering failed, check %s smpps logs for more details' % system_id)
        except Exception as e:
            self.log.error('Throwing SMPP/DLR [msgid:%s] to (%s): %r.', msgid, system_id, e)

            # List of exceptions after which, no further retrying shall be made
            noRetryExceptions = [SmppsNotSetError]

            retry = True
            for noRetryException in noRetryExceptions:
                if isinstance(e, noRetryException):
                    retry = False
                    break

            # Requeue message for later retry
            if retry and self.getThrowingRetrials(message) <= self.config.max_retries:
                self.log.debug('Message try-count is %s [msgid:%s]: requeuing',
                               self.getThrowingRetrials(message), msgid)
                yield self.rejectAndRequeueMessage(message)
            elif retry and self.getThrowingRetrials(message) > self.config.max_retries:
                self.log.warn('Message is no more processed after receiving "%s" error', str(e))
                yield self.rejectMessage(message)
            else:
                self.log.warn('Message try-count is %s [msgid:%s]: purged from queue',
                              self.getThrowingRetrials(message), msgid)
                yield self.rejectMessage(message)
        else:
            # Everything is okay ? then:
            yield self.ackMessage(message)

    @defer.inlineCallbacks
    def dlr_throwing_callback(self, message):
        Thrower.throwing_callback(self, message)

        if message.routing_key == 'dlr_thrower.http':
            yield self.http_dlr_callback(message)
        elif message.routing_key == 'dlr_thrower.smpps':
            yield self.smpp_dlr_callback(message)
        else:
            self.log.error('Unknown routing_key in dlr_throwing_callback: %s', message.routing_key)
            yield self.rejectMessage(message)
Esempio n. 22
0
class SMPPClientSMListener(object):
    """
    This is a listener object instantiated for every new SMPP connection, it is responsible of handling
    SubmitSm, DeliverSm and SubmitSm PDUs for a given SMPP connection
    """

    def __init__(self, config, SMPPClientFactory, amqpBroker, redisClient, RouterPB=None, interceptorpb_client=None):
        self.config = config
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.RouterPB = RouterPB
        self.interceptorpb_client = interceptorpb_client
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.submit_retrials = {}
        self.qosTimer = None

        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

    def setSubmitSmQ(self, queue):
        self.log.debug('Setting a new submit_sm_q: %s', queue)
        self.submit_sm_q = queue

    def clearRejectTimer(self, msgid):
        if msgid in self.rejectTimers:
            timer = self.rejectTimers[msgid]
            if timer.active():
                timer.cancel()
            del self.rejectTimers[msgid]

    def clearRejectTimers(self):
        for msgid, timer in self.rejectTimers.items():
            if timer.active():
                timer.cancel()
            del self.rejectTimers[msgid]

    def clearQosTimer(self):
        if self.qosTimer is not None and self.qosTimer.called is False:
            self.qosTimer.cancel()
            self.qosTimer = None

    def clearAllTimers(self):
        self.clearQosTimer()
        self.clearRejectTimers()

    @defer.inlineCallbacks
    def rejectAndRequeueMessage(self, message, delay=True):
        msgid = message.content.properties['message-id']

        if delay:
            # Use configured requeue_delay or specific one
            if not isinstance(delay, bool):
                requeue_delay = delay
            else:
                requeue_delay = self.SMPPClientFactory.config.requeue_delay

            self.log.debug("Requeuing SubmitSmPDU[%s] in %s seconds",
                           msgid, requeue_delay)

            # Requeue the message with a delay
            timer = reactor.callLater(requeue_delay,
                                      self.rejectMessage,
                                      message=message,
                                      requeue=1)

            # If any, clear timer before setting a new one
            self.clearRejectTimer(msgid)

            self.rejectTimers[msgid] = timer
            defer.returnValue(timer)
        else:
            self.log.debug("Requeuing SubmitSmPDU[%s] without delay", msgid)
            yield self.rejectMessage(message, requeue=1)

    @defer.inlineCallbacks
    def rejectMessage(self, message, requeue=0):
        yield self.amqpBroker.chan.basic_reject(delivery_tag=message.delivery_tag, requeue=requeue)

    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    @defer.inlineCallbacks
    def submit_sm_callback(self, message):
        """This callback is a queue listener
        it is called whenever a message was consumed from queue
        c.f. test_amqp.ConsumeTestCase for use cases
        """
        msgid = None
        try:
            msgid = message.content.properties['message-id']
            SubmitSmPDU = pickle.loads(message.content.body)

            self.submit_sm_q.get().addCallback(self.submit_sm_callback).addErrback(self.submit_sm_errback)

            self.log.debug("Callbacked a submit_sm with a SubmitSmPDU[%s] (?): %s", msgid, SubmitSmPDU)

            # Update submit_sm retrial tracker
            if msgid in self.submit_retrials:
                self.submit_retrials[msgid] += 1
            else:
                self.submit_retrials[msgid] = 1

            if self.qos_last_submit_sm_at is None:
                self.qos_last_submit_sm_at = datetime(1970, 1, 1)

            if self.SMPPClientFactory.config.submit_sm_throughput > 0:
                # QoS throttling
                qos_throughput_second = 1 / float(self.SMPPClientFactory.config.submit_sm_throughput)
                qos_throughput_ysecond_td = timedelta(microseconds=qos_throughput_second * 1000000)
                qos_delay = datetime.now() - self.qos_last_submit_sm_at
                if qos_delay < qos_throughput_ysecond_td:
                    qos_slow_down = float((qos_throughput_ysecond_td - qos_delay).microseconds) / 1000000
                    # We're faster than submit_sm_throughput,
                    # slow down before taking a new message from the queue
                    self.log.debug(
                        "QoS: submit_sm_callback faster (%s) than throughput (%s), slowing down %ss (requeuing).",
                        qos_delay, qos_throughput_ysecond_td, qos_slow_down)

                    # Relaunch queue callbacking after qos_slow_down seconds
                    # self.qosTimer = task.deferLater(reactor, qos_slow_down, self.submit_sm_q.get)
                    # self.qosTimer.addCallback(self.submit_sm_callback).addErrback(self.submit_sm_errback)
                    # Requeue the message
                    yield self.rejectAndRequeueMessage(message, delay=qos_slow_down)
                    defer.returnValue(False)

                self.qos_last_submit_sm_at = datetime.now()

            # Verify if message is a SubmitSm PDU
            if isinstance(SubmitSmPDU, SubmitSM) is False:
                self.log.error(
                    "Received object[%s] is not an instance of SubmitSm: discarding this unknown object from queue",
                    msgid)
                yield self.rejectMessage(message)
                defer.returnValue(False)
            # If the message has expired in the queue
            if 'headers' in message.content.properties and 'expiration' in message.content.properties['headers']:
                expiration_datetime = parser.parse(message.content.properties['headers']['expiration'])
                if expiration_datetime < datetime.now():
                    self.log.info(
                        "Discarding expired message[%s]: expiration is %s", msgid, expiration_datetime)
                    yield self.rejectMessage(message)
                    defer.returnValue(False)
            # SMPP Client should be already connected
            if self.SMPPClientFactory.smpp is None:
                created_at = parser.parse(message.content.properties['headers']['created_at'])
                msgAge = datetime.now() - created_at
                if msgAge.seconds > self.config.submit_max_age_smppc_not_ready:
                    self.log.error(
                        "SMPPC [cid:%s] is not connected: Discarding (#%s) SubmitSmPDU[%s], over-aged %s seconds.",
                        self.SMPPClientFactory.config.id, self.submit_retrials[msgid],
                        msgid, msgAge.seconds)
                    yield self.rejectMessage(message)
                    defer.returnValue(False)
                else:
                    if self.config.submit_retrial_delay_smppc_not_ready:
                        delay_str = ' with delay %s seconds' % self.config.submit_retrial_delay_smppc_not_ready
                    else:
                        delay_str = ''
                    self.log.error(
                        "SMPPC [cid:%s] is not connected: Requeuing (#%s) SubmitSmPDU[%s]%s, aged %s seconds.",
                        self.SMPPClientFactory.config.id, self.submit_retrials[msgid],
                        msgid, delay_str, msgAge.seconds)
                    yield self.rejectAndRequeueMessage(message,
                                                       delay=self.config.submit_retrial_delay_smppc_not_ready)
                    defer.returnValue(False)
            # SMPP Client should be already bound as transceiver or transmitter
            if self.SMPPClientFactory.smpp.isBound() is False:
                created_at = parser.parse(message.content.properties['headers']['created_at'])
                msgAge = datetime.now() - created_at
                if msgAge.seconds > self.config.submit_max_age_smppc_not_ready:
                    self.log.error(
                        "SMPPC [cid:%s] is not bound: Discarding (#%s) SubmitSmPDU[%s], over-aged %s seconds.",
                        self.SMPPClientFactory.config.id, self.submit_retrials[msgid],
                        msgid, msgAge.seconds)
                    yield self.rejectMessage(message)
                    defer.returnValue(False)
                else:
                    if self.config.submit_retrial_delay_smppc_not_ready:
                        delay_str = ' with delay %s seconds' % self.config.submit_retrial_delay_smppc_not_ready
                    else:
                        delay_str = ''
                    self.log.error("SMPPC [cid:%s] is not bound: Requeuing (#%s) SubmitSmPDU[%s]%s, aged %s seconds.",
                                   self.SMPPClientFactory.config.id, self.submit_retrials[msgid],
                                   msgid, delay_str, msgAge)
                    yield self.rejectAndRequeueMessage(
                        message, delay=self.config.submit_retrial_delay_smppc_not_ready)
                    defer.returnValue(False)

            # Finally: send the sms !
            self.log.debug("Sending SubmitSmPDU[%s] through SMPPClientFactory [cid:%s]",
                           msgid, self.SMPPClientFactory.config.id)
            d = self.SMPPClientFactory.smpp.sendDataRequest(SubmitSmPDU)
            d.addCallback(self.submit_sm_resp_event, message)
            yield d
        except SMPPRequestTimoutError:
            self.log.error("SubmitSmPDU[%s] request timed out through [cid:%s], message requeued.",
                           msgid, self.SMPPClientFactory.config.id)
            self.rejectAndRequeueMessage(message)
            defer.returnValue(False)
        except LongSubmitSmTransactionError as e:
            self.log.error("Long SubmitSmPDU[%s] error in [cid:%s], message requeued: %s",
                           msgid, self.SMPPClientFactory.config.id, e.message)
            self.rejectAndRequeueMessage(message)
            defer.returnValue(False)
        except Exception as e:
            self.log.critical("Rejecting SubmitSmPDU[%s] through [cid:%s] for an unknown error (%s): %s",
                              msgid, self.SMPPClientFactory.config.id, type(e), e)
            self.rejectMessage(message)
            defer.returnValue(False)

    @defer.inlineCallbacks
    def submit_sm_resp_event(self, r, amqpMessage):
        msgid = amqpMessage.content.properties['message-id']
        total_bill_amount = None
        will_be_retried = False

        try:
            submit_sm_resp_bill = pickle.loads(
                amqpMessage.content.properties['headers']['submit_sm_bill']).getSubmitSmRespBill()

            if r.response.status == CommandStatus.ESME_ROK:
                # No more retrials !
                del self.submit_retrials[msgid]

                # Get bill information
                total_bill_amount = 0.0
                if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts() > 0:
                    total_bill_amount = submit_sm_resp_bill.getTotalAmounts()

                # UDH is set ?
                UDHI_INDICATOR_SET = False
                if hasattr(r.request.params['esm_class'], 'gsmFeatures'):
                    for gsmFeature in r.request.params['esm_class'].gsmFeatures:
                        if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                            UDHI_INDICATOR_SET = True
                            break

                # What type of splitting ?
                splitMethod = None
                if 'sar_msg_ref_num' in r.request.params:
                    splitMethod = 'sar'
                elif UDHI_INDICATOR_SET and r.request.params['short_message'][:3] == '\x05\x00\x03':
                    splitMethod = 'udh'

                # Concatenate short_message
                if splitMethod is not None:
                    _pdu = r.request
                    if splitMethod == 'sar':
                        short_message = _pdu.params['short_message']
                    else:
                        short_message = _pdu.params['short_message'][6:]

                    while hasattr(_pdu, 'nextPdu'):
                        _pdu = _pdu.nextPdu
                        if splitMethod == 'sar':
                            short_message += _pdu.params['short_message']
                        else:
                            short_message += _pdu.params['short_message'][6:]

                        # Increase bill amount for each submit_sm_resp
                        if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts() > 0:
                            total_bill_amount += submit_sm_resp_bill.getTotalAmounts()
                else:
                    short_message = r.request.params['short_message']

                # Do not log text for privacy reasons
                # Added in #691
                if self.config.log_privacy:
                    logged_content = '** %s byte content **' % len(short_message)
                else:
                    logged_content = '%r' % short_message

                self.log.info(
                    "SMS-MT [cid:%s] [queue-msgid:%s] [smpp-msgid:%s] [status:%s] [prio:%s] [dlr:%s] [validity:%s] \
[from:%s] [to:%s] [content:%s]",
                    self.SMPPClientFactory.config.id,
                    msgid,
                    r.response.params['message_id'],
                    r.response.status,
                    amqpMessage.content.properties['priority'],
                    r.request.params['registered_delivery'].receipt,
                    'none' if ('headers' not in amqpMessage.content.properties or
                               'expiration' not in amqpMessage.content.properties['headers'])
                    else amqpMessage.content.properties['headers']['expiration'],
                    r.request.params['source_addr'],
                    r.request.params['destination_addr'],
                    logged_content)
            else:
                # Message must be retried ?
                if str(r.response.status) in self.config.submit_error_retrial:
                    retrial = self.config.submit_error_retrial[str(r.response.status)]

                    # Still have some retries to go ?
                    if self.submit_retrials[msgid] < retrial['count']:
                        # Requeue the message for later redelivery
                        yield self.rejectAndRequeueMessage(amqpMessage, delay=retrial['delay'])
                        will_be_retried = True
                    else:
                        # Prevent this list from over-growing
                        del self.submit_retrials[msgid]

                # Do not log text for privacy reasons
                # Added in #691
                if self.config.log_privacy:
                    logged_content = '** %s byte content **' % len(r.request.params['short_message'])
                else:
                    logged_content = '%r' % r.request.params['short_message']

                # Log the message
                self.log.info(
                    "SMS-MT [cid:%s] [queue-msgid:%s] [status:ERROR/%s] [retry:%s] [prio:%s] [dlr:%s] [validity:%s] \
[from:%s] [to:%s] [content:%s]",
                    self.SMPPClientFactory.config.id,
                    msgid,
                    r.response.status,
                    will_be_retried,
                    amqpMessage.content.properties['priority'],
                    r.request.params['registered_delivery'].receipt,
                    'none' if ('headers' not in amqpMessage.content.properties or
                               'expiration' not in amqpMessage.content.properties['headers'])
                    else amqpMessage.content.properties['headers']['expiration'],
                    r.request.params['source_addr'],
                    r.request.params['destination_addr'],
                    logged_content)

            # It is a final submit_sm_resp !
            if not will_be_retried:
                # Cancel any mapped rejectTimer to this message
                # (in case this message was rejected in the past)
                self.clearRejectTimer(msgid)
                self.log.debug("ACKing amqpMessage [%s] having routing_key [%s]",
                               msgid, amqpMessage.routing_key)
                # ACK the message in queue, this will remove it from the queue
                yield self.ackMessage(amqpMessage)

            # Send DLR to DLRLookup
            if r.response.status == CommandStatus.ESME_ROK:
                dlr = DLR(pdu_type=r.response.id, msgid=msgid, status=r.response.status,
                          smpp_msgid=r.response.params['message_id'])
            else:
                dlr = DLR(pdu_type=r.response.id, msgid=msgid, status=r.response.status)
            yield self.amqpBroker.publish(exchange='messaging', routing_key='dlr.submit_sm_resp', content=dlr)

            # Bill will be charged by bill_request.submit_sm_resp.UID queue consumer
            if total_bill_amount > 0:
                pubQueueName = 'bill_request.submit_sm_resp.%s' % submit_sm_resp_bill.user.uid
                content = SubmitSmRespBillContent(submit_sm_resp_bill.bid,
                                                  submit_sm_resp_bill.user.uid, total_bill_amount)
                self.log.debug(
                    "Requesting a SubmitSmRespBillContent from a bill [bid:%s] with routing_key[%s]: %s",
                    submit_sm_resp_bill.bid, pubQueueName, total_bill_amount)
                yield self.amqpBroker.publish(exchange='billing',
                                              routing_key=pubQueueName,
                                              content=content)

            if self.config.publish_submit_sm_resp:
                # Send back submit_sm_resp to submit.sm.resp.CID queue
                # There's no actual listeners on this queue, it can be used to
                # track submit_sm_resp messages from a 3rd party app
                content = SubmitSmRespContent(r.response, msgid, pickleProtocol=self.pickleProtocol)
                self.log.debug("Sending back SubmitSmRespContent[%s] with routing_key[%s]",
                               msgid, amqpMessage.content.properties['reply-to'])
                yield self.amqpBroker.publish(exchange='messaging',
                                              routing_key=amqpMessage.content.properties['reply-to'],
                                              content=content)
        except Exception as e:
            self.log.error('(%s) while handling submit_sm_resp pdu for msgid:%s: %s', type(e), msgid, e)
        else:
            if will_be_retried:
                defer.returnValue(False)

    def submit_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didn't find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) is None:
            # @todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occurred inside submit_sm_callback
            # - the qosTimer has been cancelled (self.clearQosTimer())
            try:
                error.raiseException()
            except Exception as e:
                self.log.error("Error in submit_sm_errback (%s): %s", type(e), e)

    @defer.inlineCallbacks
    def concatDeliverSMs(self, HSetReturn, hashKey, splitMethod, total_segments, msg_ref_num, segment_seqnum):
        if HSetReturn == 0:
            self.log.warn('This hashKey %s already exists, will not reset it !', hashKey)
            return

        # @TODO: longDeliverSm part expiry must be configurable
        yield self.redisClient.expire(hashKey, 300)

        # This is the last part
        if segment_seqnum == total_segments:
            hvals = yield self.redisClient.hvals(hashKey)
            if len(hvals) != total_segments:
                self.log.warn(
                    'Received the last part (msg_ref_num:%s) and did not find all parts in redis, data lost !',
                    msg_ref_num)
                return

            # Get PDUs
            pdus = {}
            for pickledValue in hvals:
                value = pickle.loads(pickledValue)

                pdus[value['segment_seqnum']] = value['pdu']

            # Where is the message content to be found ?
            if 'short_message' in pdus[1].params:
                msg_content_key = 'short_message'
            elif 'message_payload' in pdus[1].params:
                msg_content_key = 'message_payload'
            else:
                self.log.warn('Cannot find message content in first pdu params: %s', pdus[1].params)
                return

            # Build concat_message_content
            concat_message_content = ''
            for i in range(total_segments):
                if splitMethod == 'sar':
                    concat_message_content += pdus[i + 1].params[msg_content_key]
                else:
                    concat_message_content += pdus[i + 1].params[msg_content_key][6:]

            # Build the final pdu and return it back to deliver_sm_event
            pdu = pdus[1]  # Take the first part as a base of work
            # 1. Remove message splitting information from pdu
            if splitMethod == 'sar':
                del pdu.params['sar_segment_seqnum']
                del pdu.params['sar_total_segments']
                del pdu.params['sar_msg_ref_num']
            else:
                pdu.params['esm_class'] = None
            # 2. Set the new concat_message_content
            pdu.params[msg_content_key] = concat_message_content

            routable = RoutableDeliverSm(pdu, Connector(self.SMPPClientFactory.config.id))
            yield self.deliver_sm_event_post_interception(routable=routable, smpp=None, concatenated=True)

    def code_dlr_msgid(self, pdu):
        """Code the dlr msg id accordingly to SMPPc's dlr_msg_id_bases value"""

        try:
            if isinstance(pdu, DeliverSM):
                if self.SMPPClientFactory.config.dlr_msg_id_bases == 1:
                    ret = ('%x' % int(pdu.dlr['id'])).upper().lstrip('0')
                elif self.SMPPClientFactory.config.dlr_msg_id_bases == 2:
                    ret = int(str(pdu.dlr['id']), 16)
                else:
                    ret = str(pdu.dlr['id']).upper().lstrip('0')
            else:
                # TODO: code dlr for submit_sm_resp maybe ? TBC
                ret = str(pdu.dlr['id']).upper().lstrip('0')
        except Exception as e:
            self.log.error('code_dlr_msgid, cannot code msgid [%s] with dlr_msg_id_bases:%s',
                           pdu.dlr['id'], self.SMPPClientFactory.config.dlr_msg_id_bases)
            self.log.error('code_dlr_msgid, error details: %s', e)
            ret = str(pdu.dlr['id']).upper().lstrip('0')

        self.log.debug('code_dlr_msgid: %s coded to %s', pdu.dlr['id'], ret)
        return ret

    def deliver_sm_event_interceptor(self, smpp, pdu):
        self.log.debug('Intercepting deliver_sm event in smppc %s', self.SMPPClientFactory.config.id)

        if self.RouterPB is None:
            self.log.error(
                '(deliver_sm_event_interceptor/%s) RouterPB not set: deliver_sm will not be routed',
                self.SMPPClientFactory.config.id)
            return

        # Prepare for interception
        # this is a temporary routable instance to be used in interception
        routable = RoutableDeliverSm(pdu, Connector(self.SMPPClientFactory.config.id))

        # Interception inline
        # @TODO: make Interception in a thread, just like httpapi interception
        interceptor = self.RouterPB.getMOInterceptionTable().getInterceptorFor(routable)
        if interceptor is not None:
            self.log.debug("RouterPB selected %s interceptor for this DeliverSmPDU", interceptor)
            if self.interceptorpb_client is None:
                smpp.factory.stats.inc('interceptor_error_count')
                self.log.error("InterceptorPB not set !")
                raise InterceptorNotSetError('InterceptorPB not set !')
            if not self.interceptorpb_client.isConnected:
                smpp.factory.stats.inc('interceptor_error_count')
                self.log.error("InterceptorPB not connected !")
                raise InterceptorNotConnectedError('InterceptorPB not connected !')

            script = interceptor.getScript()
            self.log.debug("Interceptor script loaded: %s", script)

            # Run !
            d = self.interceptorpb_client.run_script(script, routable)
            d.addCallback(self.deliver_sm_event_post_interception, routable=routable, smpp=smpp)
            d.addErrback(self.deliver_sm_event_post_interception)
            return d
        else:
            return self.deliver_sm_event_post_interception(routable=routable, smpp=smpp)

    @defer.inlineCallbacks
    def deliver_sm_event_post_interception(self, *args, **kw):
        """This event is called whenever a deliver_sm pdu is received through a SMPPc
        It will hand the pdu to the router or a dlr thrower (depending if its a DLR or not).

        Note: this event will catch data_sm pdus as well
        """

        try:
            # Control args
            if 'smpp' not in kw or 'routable' not in kw:
                self.log.error(
                    'deliver_sm_event_post_interception missing arguments after interception: %s', kw)
                raise InterceptorRunError(
                    'deliver_sm_event_post_interception missing arguments after interception')

            # Set defaults
            smpp = kw['smpp']
            routable = kw['routable']

            if 'concatenated' in kw:
                concatenated = kw['concatenated']
            else:
                concatenated = False

            # Get message_content
            if 'short_message' in routable.pdu.params and len(routable.pdu.params['short_message']) > 0:
                message_content = routable.pdu.params['short_message']
            elif 'message_payload' in routable.pdu.params:
                message_content = routable.pdu.params['message_payload']
            elif 'short_message' in routable.pdu.params:
                message_content = routable.pdu.params['short_message']
            else:
                message_content = None

            # Post interception:
            if len(args) == 1:
                if isinstance(args[0], bool) and not args[0]:
                    smpp.factory.stats.inc('interceptor_error_count')
                    self.log.error('Failed running interception script, got a False return.')
                    raise InterceptorRunError('Failed running interception script, check log for details')
                elif isinstance(args[0], dict) and args[0]['smpp_status'] > 0:
                    smpp.factory.stats.inc('interceptor_error_count')
                    self.log.info(
                        'Interceptor script returned %s smpp_status error.', args[0]['smpp_status'])
                    raise DeliverSmInterceptionError(code=args[0]['smpp_status'])
                elif isinstance(args[0], str):
                    smpp.factory.stats.inc('interceptor_count')
                    routable = pickle.loads(args[0])
                else:
                    smpp.factory.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got the following return: %s', args[0])
                    raise InterceptorRunError(
                        'Failed running interception script, got the following return: %s' % args[0])

            self.log.debug('Handling deliver_sm_event_post_interception event for smppc: %s',
                           self.SMPPClientFactory.config.id)

            routable.pdu.dlr = self.SMPPOperationFactory.isDeliveryReceipt(routable.pdu)
            content = DeliverSmContent(routable,
                                       self.SMPPClientFactory.config.id,
                                       pickleProtocol=self.pickleProtocol,
                                       concatenated=concatenated)
            msgid = content.properties['message-id']

            if routable.pdu.dlr is None:
                # We have a SMS-MO

                # UDH is set ?
                UDHI_INDICATOR_SET = False
                if 'esm_class' in routable.pdu.params and hasattr(routable.pdu.params['esm_class'], 'gsmFeatures'):
                    for gsmFeature in routable.pdu.params['esm_class'].gsmFeatures:
                        if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                            UDHI_INDICATOR_SET = True
                            break

                not_class2 = True
                if 'data_coding' in routable.pdu.params:
                    dcs = routable.pdu.params['data_coding']
                    if (str(dcs.scheme) == 'GSM_MESSAGE_CLASS') and (dcs.schemeData is not None):
                        not_class2 = (str(dcs.schemeData.msgClass) != 'CLASS_2')

                splitMethod = None
                # Is it a part of a long message ?
                if 'sar_msg_ref_num' in routable.pdu.params:
                    splitMethod = 'sar'
                    total_segments = routable.pdu.params['sar_total_segments']
                    segment_seqnum = routable.pdu.params['sar_segment_seqnum']
                    msg_ref_num = routable.pdu.params['sar_msg_ref_num']
                    self.log.debug(
                        'Received SMS-MO part [queue-msgid:%s] using SAR: ttl_segments=%s, segment_sn=%s, msgref=%s',
                        msgid, total_segments, segment_seqnum, msg_ref_num)
                elif UDHI_INDICATOR_SET and not_class2 and message_content[:3] == '\x05\x00\x03':
                    splitMethod = 'udh'
                    total_segments = struct.unpack('!B', message_content[4])[0]
                    segment_seqnum = struct.unpack('!B', message_content[5])[0]
                    msg_ref_num = struct.unpack('!B', message_content[3])[0]
                    self.log.debug(
                        'Received SMS-MO part [queue-msgid:%s] using UDH: ttl_segments=%s, segment_sn=%s, msgref=%s',
                        msgid, total_segments, segment_seqnum, msg_ref_num)

                if splitMethod is None:
                    # It's a simple short message or a part of a concatenated message
                    routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                    self.log.debug("Publishing DeliverSmContent[%s] with routing_key[%s]",
                                   msgid, routing_key)
                    yield self.amqpBroker.publish(exchange='messaging',
                                                  routing_key=routing_key, content=content)

                    # Get values from data_sm or deliver_sm
                    priority_flag = None
                    if 'priority_flag' in routable.pdu.params:
                        priority_flag = routable.pdu.params['priority_flag']
                    validity_period = None
                    if 'validity_period' in routable.pdu.params:
                        validity_period = routable.pdu.params['validity_period']

                    # Do not log text for privacy reasons
                    # Added in #691
                    if self.config.log_privacy:
                        logged_content = '** %s byte content **' % len(message_content)
                    else:
                        logged_content = '%r' % message_content

                    self.log.info(
                        "SMS-MO [cid:%s] [queue-msgid:%s] [status:%s] [prio:%s] [validity:%s] [from:%s] [to:%s] \
[content:%s]",
                        self.SMPPClientFactory.config.id,
                        msgid,
                        routable.pdu.status,
                        priority_flag,
                        validity_period,
                        routable.pdu.params['source_addr'],
                        routable.pdu.params['destination_addr'],
                        logged_content)
                else:
                    # Long message part received
                    if self.redisClient is None:
                        self.log.critical(
                            'Invalid RC found while receiving part of long DeliverSm [queue-msgid:%s], MSG IS LOST !',
                            msgid)
                    else:
                        # Save it to redis
                        hashKey = "longDeliverSm:%s:%s:%s" % (
                            self.SMPPClientFactory.config.id,
                            msg_ref_num,
                            routable.pdu.params['destination_addr'])
                        hashValues = {'pdu': routable.pdu,
                                      'total_segments': total_segments,
                                      'msg_ref_num': msg_ref_num,
                                      'segment_seqnum': segment_seqnum}
                        yield self.redisClient.hset(
                            hashKey, segment_seqnum, pickle.dumps(hashValues, self.pickleProtocol)).addCallback(
                            self.concatDeliverSMs,
                            hashKey,
                            splitMethod,
                            total_segments,
                            msg_ref_num,
                            segment_seqnum)

                        self.log.info(
                            "DeliverSmContent[%s] is part of long msg of (%s), will be enqueued after concatenation.",
                            msgid, total_segments)

                        # Flag it as "will_be_concatenated" and publish it to router
                        routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                        self.log.debug("Publishing DeliverSmContent[%s](flagged:wbc) with routing_key[%s]",
                                       msgid, routing_key)
                        content.properties['headers']['will_be_concatenated'] = True
                        yield self.amqpBroker.publish(exchange='messaging',
                                                      routing_key=routing_key, content=content)
            else:
                # This is a DLR !
                # Send DLR to DLRLookup
                yield self.amqpBroker.publish(exchange='messaging',
                                              routing_key='dlr.deliver_sm',
                                              content=DLR(pdu_type=routable.pdu.id,
                                                          msgid=self.code_dlr_msgid(routable.pdu),
                                                          status=routable.pdu.dlr['stat'],
                                                          cid=self.SMPPClientFactory.config.id,
                                                          dlr_details=routable.pdu.dlr))
        except (InterceptorRunError, DeliverSmInterceptionError) as e:
            # Do not log text for privacy reasons
            # Added in #691
            if self.config.log_privacy:
                logged_content = '** %s byte content **' % len(message_content)
            else:
                logged_content = '%r' % message_content

            self.log.info("SMS-MO [cid:%s] [i-status:%s] [from:%s] [to:%s] [content:%s]",
                          self.SMPPClientFactory.config.id,
                          e.status,
                          routable.pdu.params['source_addr'],
                          routable.pdu.params['destination_addr'],
                          logged_content)

            # Known exception handling
            defer.returnValue(DataHandlerResponse(status=e.status))
        except Exception as e:
            # Unknown exception handling
            self.log.critical('Got an unknown exception (%s): %s', type(e), e)
            defer.returnValue(DataHandlerResponse(status=CommandStatus.ESME_RUNKNOWNERR))
Esempio n. 23
0
 def setUp(self):
     self.opFactory = SMPPOperationFactory(SMPPClientConfig(id='test-id'))
Esempio n. 24
0
 def setUp(self):
     self.opFactory = SMPPOperationFactory(SMPPClientConfig(id='test-id'))
Esempio n. 25
0
class Rate(Resource):
    isleaf = True

    def __init__(self, HTTPApiConfig, RouterPB, stats, log,
                 interceptorpb_client):
        Resource.__init__(self)

        self.RouterPB = RouterPB
        self.stats = stats
        self.log = log
        self.interceptorpb_client = interceptorpb_client

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(
            long_content_max_parts=HTTPApiConfig.long_content_max_parts,
            long_content_split=HTTPApiConfig.long_content_split)

    @defer.inlineCallbacks
    def route_routable(self, request):
        try:
            # Do we have a hex-content ?
            if 'hex-content' not in request.args:
                # Convert utf8 to GSM 03.38
                if request.args['coding'][0] == '0':
                    short_message = gsm_encode(
                        request.args['content'][0].decode('utf-8'))
                else:
                    # Otherwise forward it as is
                    short_message = request.args['content'][0]
            else:
                # Otherwise convert hex to bin
                short_message = hex2bin(request.args['hex-content'][0])

            # Authentication
            user = self.RouterPB.authenticateUser(
                username=request.args['username'][0],
                password=request.args['password'][0])
            if user is None:
                self.stats.inc('auth_error_count')

                self.log.debug(
                    "Authentication failure for username:%s and password:%s",
                    request.args['username'][0], request.args['password'][0])
                self.log.error("Authentication failure for username:%s",
                               request.args['username'][0])
                raise AuthenticationError(
                    'Authentication failure for username:%s' %
                    request.args['username'][0])

            # Update CnxStatus
            user.getCnxStatus().httpapi['connects_count'] += 1
            user.getCnxStatus().httpapi['rate_request_count'] += 1
            user.getCnxStatus().httpapi['last_activity_at'] = datetime.now()

            # Build SubmitSmPDU
            SubmitSmPDU = self.opFactory.SubmitSM(
                source_add=None
                if 'from' not in request.args else request.args['from'][0],
                destination_addr=request.args['to'][0],
                short_message=short_message,
                data_coding=int(request.args['coding'][0]),
            )
            self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU)

            # Make Credential validation
            v = HttpAPICredentialValidator('Rate',
                                           user,
                                           request,
                                           submit_sm=SubmitSmPDU)
            v.validate()

            # Update SubmitSmPDU by default values from user MtMessagingCredential
            SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU)

            # Prepare for interception than routing
            routable = RoutableSubmitSm(SubmitSmPDU, user)
            self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable,
                           SubmitSmPDU)

            # Should we tag the routable ?
            tags = []
            if 'tags' in request.args:
                tags = request.args['tags'][0].split(',')
                for tag in tags:
                    routable.addTag(tag)
                    self.log.debug('Tagged routable %s: +%s', routable, tag)

            # Intercept
            interceptor = self.RouterPB.getMTInterceptionTable(
            ).getInterceptorFor(routable)
            if interceptor is not None:
                self.log.debug(
                    "RouterPB selected %s interceptor for this SubmitSmPDU",
                    interceptor)
                if self.interceptorpb_client is None:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not set !")
                    raise InterceptorNotSetError('InterceptorPB not set !')
                if not self.interceptorpb_client.isConnected:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not connected !")
                    raise InterceptorNotConnectedError(
                        'InterceptorPB not connected !')

                script = interceptor.getScript()
                self.log.debug("Interceptor script loaded: %s", script)

                # Run !
                r = yield self.interceptorpb_client.run_script(
                    script, routable)
                if isinstance(r, dict) and r['http_status'] != 200:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Interceptor script returned %s http_status error.',
                        r['http_status'])
                    raise InterceptorRunError(
                        code=r['http_status'],
                        message='Interception specific error code %s' %
                        r['http_status'])
                elif isinstance(r, str):
                    self.stats.inc('interceptor_count')
                    routable = pickle.loads(r)
                else:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got the following return: %s',
                        r)
                    raise InterceptorRunError(
                        message=
                        'Failed running interception script, check log for details'
                    )

            # Routing
            route = self.RouterPB.getMTRoutingTable().getRouteFor(routable)
            if route is None:
                self.log.error(
                    "No route matched from user %s for SubmitSmPDU: %s", user,
                    SubmitSmPDU)
                raise RouteNotFoundError("No route found")

            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU", route)

            # Get number of PDUs to be sent (for billing purpose)
            _pdu = SubmitSmPDU
            submit_sm_count = 1
            while hasattr(_pdu, 'nextPdu'):
                _pdu = _pdu.nextPdu
                submit_sm_count += 1

            # Get the bill
            bill = route.getBillFor(user)

            response = {
                'return': {
                    'unit_rate': bill.getTotalAmounts(),
                    'submit_sm_count': submit_sm_count
                },
                'status': 200
            }
        except Exception as e:
            self.log.error("Error: %s", e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}
        finally:
            self.log.debug("Returning %s to %s.", response,
                           request.getClientIP())

            # Return message
            if response['return'] is None:
                response['return'] = 'System error'
                request.setResponseCode(500)
            else:
                request.setResponseCode(response['status'])

            request.write(json.dumps(response['return']))
            request.finish()

    def render(self, request):
        """
        /rate request processing

        Note: This method will indicate the rate of the message once sent
        """

        self.log.debug("Rendering /rate response with args: %s from %s",
                       request.args, request.getClientIP())
        request.responseHeaders.addRawHeader(b"content-type",
                                             b"application/json")
        response = {'return': None, 'status': 200}

        self.stats.inc('request_count')
        self.stats.set('last_request_at', datetime.now())

        try:
            # Validation (must be almost the same params as /send service)
            fields = {
                'to': {
                    'optional': False,
                    'pattern': re.compile(r'^\+{0,1}\d+$')
                },
                'from': {
                    'optional': True
                },
                'coding': {
                    'optional': True,
                    'pattern':
                    re.compile(r'^(0|1|2|3|4|5|6|7|8|9|10|13|14){1}$')
                },
                'username': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,15}$')
                },
                'password': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,8}$')
                },
                # Priority validation pattern can be validated/filtered further more
                # through HttpAPICredentialValidator
                'priority': {
                    'optional': True,
                    'pattern': re.compile(r'^[0-3]$')
                },
                # Validity period validation pattern can be validated/filtered further more
                # through HttpAPICredentialValidator
                'validity-period': {
                    'optional': True,
                    'pattern': re.compile(r'^\d+$')
                },
                'tags': {
                    'optional': True,
                    'pattern': re.compile(r'^([-a-zA-Z0-9,])*$')
                },
                'content': {
                    'optional': True
                },
                'hex-content': {
                    'optional': True
                },
            }

            # Default coding is 0 when not provided
            if 'coding' not in request.args:
                request.args['coding'] = ['0']

            # Content is optional, defaults to empty content string
            if 'hex-content' not in request.args and 'content' not in request.args:
                request.args['content'] = ['']

            # Make validation
            v = UrlArgsValidator(request, fields)
            v.validate()

            # Check if have content --OR-- hex-content
            # @TODO: make this inside UrlArgsValidator !
            if 'content' in request.args and 'hex-content' in request.args:
                raise UrlArgsValidationError(
                    "content and hex-content cannot be used both in same request."
                )

            # Continue routing in a separate thread
            reactor.callFromThread(self.route_routable, request=request)
        except Exception as e:
            self.log.error("Error: %s", e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}

            self.log.debug("Returning %s to %s.", response,
                           request.getClientIP())

            # Return message
            if response['return'] is None:
                response['return'] = 'System error'
                request.setResponseCode(500)
            else:
                request.setResponseCode(response['status'])
            return json.dumps(response['return'])
        else:
            return NOT_DONE_YET
Esempio n. 26
0
class Rate(Resource):
    def __init__(self, HTTPApiConfig, RouterPB, stats, log):
        Resource.__init__(self)

        self.RouterPB = RouterPB
        self.log = log
        self.stats = stats

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(
            long_content_max_parts=HTTPApiConfig.long_content_max_parts,
            long_content_split=HTTPApiConfig.long_content_split)

    def render(self, request):
        """
        /rate request processing

        Note: This method will indicate the rate of the message once sent
        """

        self.log.debug("Rendering /rate response with args: %s from %s" %
                       (request.args, request.getClientIP()))
        request.responseHeaders.addRawHeader(b"content-type",
                                             b"application/json")
        response = {'return': None, 'status': 200}

        self.stats.inc('request_count')
        self.stats.set('last_request_at', datetime.now())

        try:
            # Validation (must be almost the same params as /send service)
            fields = {
                'to': {
                    'optional': False,
                    'pattern': re.compile(r'^\+{0,1}\d+$')
                },
                'from': {
                    'optional': True
                },
                'coding': {
                    'optional': True,
                    'pattern':
                    re.compile(r'^(0|1|2|3|4|5|6|7|8|9|10|13|14){1}$')
                },
                'username': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,9}$')
                },
                'password': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,9}$')
                },
                # Priority validation pattern can be validated/filtered further more through HttpAPICredentialValidator
                'priority': {
                    'optional': True,
                    'pattern': re.compile(r'^[0-3]$')
                },
                # Validity period validation pattern can be validated/filtered further more through HttpAPICredentialValidator
                'validity-period': {
                    'optional': True,
                    'pattern': re.compile(r'^\d+$')
                },
                'content': {
                    'optional': True
                },
            }

            # Default coding is 0 when not provided
            if 'coding' not in request.args:
                request.args['coding'] = ['0']

            # Content is optional, defaults to empty string
            if 'content' not in request.args:
                request.args['content'] = ['']

            # Make validation
            v = UrlArgsValidator(request, fields)
            v.validate()

            # Authentication
            user = self.RouterPB.authenticateUser(
                username=request.args['username'][0],
                password=request.args['password'][0])
            if user is None:
                self.stats.inc('auth_error_count')

                self.log.debug(
                    "Authentication failure for username:%s and password:%s" %
                    (request.args['username'][0], request.args['password'][0]))
                self.log.error("Authentication failure for username:%s" %
                               request.args['username'][0])
                raise AuthenticationError(
                    'Authentication failure for username:%s' %
                    request.args['username'][0])

            # Update CnxStatus
            user.getCnxStatus().httpapi['connects_count'] += 1
            user.getCnxStatus().httpapi['rate_request_count'] += 1
            user.getCnxStatus().httpapi['last_activity_at'] = datetime.now()

            # Build SubmitSmPDU
            SubmitSmPDU = self.opFactory.SubmitSM(
                source_addr=None
                if 'from' not in request.args else request.args['from'][0],
                destination_addr=request.args['to'][0],
                short_message=request.args['content'][0],
                data_coding=int(request.args['coding'][0]),
            )
            self.log.debug("Built base SubmitSmPDU: %s" % SubmitSmPDU)

            # Make Credential validation
            v = HttpAPICredentialValidator('Rate',
                                           user,
                                           request,
                                           submit_sm=SubmitSmPDU)
            v.validate()

            # Update SubmitSmPDU by default values from user MtMessagingCredential
            SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU)

            # Routing
            routedConnector = None  # init
            routable = RoutableSubmitSm(SubmitSmPDU, user)
            route = self.RouterPB.getMTRoutingTable().getRouteFor(routable)
            if route is None:
                self.log.error(
                    "No route matched from user %s for SubmitSmPDU: %s" %
                    (user, SubmitSmPDU))
                raise RouteNotFoundError("No route found")

            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU" % route)
            routedConnector = route.getConnector()

            # Get number of PDUs to be sent (for billing purpose)
            _pdu = SubmitSmPDU
            submit_sm_count = 1
            while hasattr(_pdu, 'nextPdu'):
                _pdu = _pdu.nextPdu
                submit_sm_count += 1

            # Get the bill
            bill = route.getBillFor(user)

            response = {
                'return': {
                    'unit_rate': bill.getTotalAmounts(),
                    'submit_sm_count': submit_sm_count
                },
                'status': 200
            }
        except Exception, e:
            self.log.error("Error: %s" % e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}
        finally:
Esempio n. 27
0
class Send(Resource):
    def __init__(self, HTTPApiConfig, RouterPB, SMPPClientManagerPB, log):
        Resource.__init__(self)

        self.SMPPClientManagerPB = SMPPClientManagerPB
        self.RouterPB = RouterPB
        self.log = log

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(
            long_content_max_parts=HTTPApiConfig.long_content_max_parts,
            long_content_split=HTTPApiConfig.long_content_split)

    def render(self, request):
        """
        /send request processing

        Note: This method MUST behave exactly like jasmin.protocols.smpp.factory.SMPPServerFactory.submit_sm_event
        """

        self.log.debug("Rendering /send response with args: %s from %s" %
                       (request.args, request.getClientIP()))
        response = {'return': None, 'status': 200}

        # updated_request will be filled with default values where request will never get modified
        # updated_request is used for sending the SMS, request is just kept as an original request object
        updated_request = request

        try:
            # Validation
            fields = {
                'to': {
                    'optional': False,
                    'pattern': re.compile(r'^\+{0,1}\d+$')
                },
                'from': {
                    'optional': True
                },
                'coding': {
                    'optional': True,
                    'pattern':
                    re.compile(r'^(0|1|2|3|4|5|6|7|8|9|10|13|14){1}$')
                },
                'username': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,30}$')
                },
                'password': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,30}$')
                },
                # Priority validation pattern can be validated/filtered further more through HttpAPICredentialValidator
                'priority': {
                    'optional': True,
                    'pattern': re.compile(r'^[0-3]$')
                },
                'dlr': {
                    'optional': False,
                    'pattern': re.compile(r'^(yes|no)$')
                },
                'dlr-url': {
                    'optional': True,
                    'pattern': re.compile(r'^(http|https)\://.*$')
                },
                # DLR Level validation pattern can be validated/filtered further more through HttpAPICredentialValidator
                'dlr-level': {
                    'optional': True,
                    'pattern': re.compile(r'^[1-3]$')
                },
                'dlr-method': {
                    'optional': True,
                    'pattern': re.compile(r'^(get|post)$', re.IGNORECASE)
                },
                'content': {
                    'optional': False
                },
            }

            # Default coding is 0 when not provided
            if 'coding' not in updated_request.args:
                updated_request.args['coding'] = ['0']

            # Set default for undefined updated_request.arguments
            if 'dlr-url' in updated_request.args or 'dlr-level' in updated_request.args:
                updated_request.args['dlr'] = ['yes']
            if 'dlr' not in updated_request.args:
                # Setting DLR updated_request to 'no'
                updated_request.args['dlr'] = ['no']

            # Set default values
            if updated_request.args['dlr'][0] == 'yes':
                if 'dlr-level' not in updated_request.args:
                    # If DLR is requested and no dlr-level were provided, assume minimum level (1)
                    updated_request.args['dlr-level'] = [1]
                if 'dlr-method' not in updated_request.args:
                    # If DLR is requested and no dlr-method were provided, assume default (POST)
                    updated_request.args['dlr-method'] = ['POST']

            # DLR method must be uppercase
            if 'dlr-method' in updated_request.args:
                updated_request.args['dlr-method'][0] = updated_request.args[
                    'dlr-method'][0].upper()

            # Make validation
            v = UrlArgsValidator(updated_request, fields)
            v.validate()

            # Authentication
            user = self.RouterPB.authenticateUser(
                username=updated_request.args['username'][0],
                password=updated_request.args['password'][0])
            if user is None:
                self.log.debug(
                    "Authentication failure for username:%s and password:%s" %
                    (updated_request.args['username'][0],
                     updated_request.args['password'][0]))
                self.log.error("Authentication failure for username:%s" %
                               updated_request.args['username'][0])
                raise AuthenticationError(
                    'Authentication failure for username:%s' %
                    updated_request.args['username'][0])

            # Update CnxStatus
            user.CnxStatus.httpapi['connects_count'] += 1
            user.CnxStatus.httpapi['submit_sm_request_count'] += 1
            user.CnxStatus.httpapi['last_activity_at'] = datetime.now()

            # Build SubmitSmPDU
            SubmitSmPDU = self.opFactory.SubmitSM(
                source_addr=None if 'from' not in updated_request.args else
                updated_request.args['from'][0],
                destination_addr=updated_request.args['to'][0],
                short_message=updated_request.args['content'][0],
                data_coding=int(updated_request.args['coding'][0]),
            )
            self.log.debug("Built base SubmitSmPDU: %s" % SubmitSmPDU)

            # Make Credential validation
            v = HttpAPICredentialValidator('Send', user, SubmitSmPDU, request)
            v.validate()

            # Update SubmitSmPDU by default values from user MtMessagingCredential
            SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU)

            # Routing
            routedConnector = None  # init
            routable = RoutableSubmitSm(SubmitSmPDU, user)
            route = self.RouterPB.getMTRoutingTable().getRouteFor(routable)
            if route is None:
                self.log.error(
                    "No route matched from user %s for SubmitSmPDU: %s" %
                    (user, SubmitSmPDU))
                raise RouteNotFoundError("No route found")

            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU" % route)
            routedConnector = route.getConnector()

            # Set priority
            priority = 0
            if 'priority' in updated_request.args:
                priority = int(updated_request.args['priority'][0])
                SubmitSmPDU.params['priority_flag'] = priority_flag_value_map[
                    priority]
            self.log.debug("SubmitSmPDU priority is set to %s" % priority)

            # Set DLR bit mask
            # c.f. 5.2.17 registered_delivery
            ####################################################################
            # dlr-level # Signification                  # registered_delivery #
            ####################################################################
            # 1         # SMS-C level                    # x x x x x x 1 0     #
            # 2         # Terminal level (only)          # x x x x x x 0 1     #
            # 3         # SMS-C level and Terminal level # x x x x x x 0 1     #
            ####################################################################
            if updated_request.args['dlr'][0] == 'yes':
                if updated_request.args['dlr-level'][0] == '1':
                    SubmitSmPDU.params[
                        'registered_delivery'] = RegisteredDelivery(
                            RegisteredDeliveryReceipt.
                            NO_SMSC_DELIVERY_RECEIPT_REQUESTED)
                elif updated_request.args['dlr-level'][
                        0] == '2' or updated_request.args['dlr-level'][
                            0] == '3':
                    SubmitSmPDU.params[
                        'registered_delivery'] = RegisteredDelivery(
                            RegisteredDeliveryReceipt.
                            SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE)
                self.log.debug("SubmitSmPDU registered_delivery is set to %s" %
                               str(SubmitSmPDU.params['registered_delivery']))

                dlr_level = int(updated_request.args['dlr-level'][0])
                if 'dlr-url' in updated_request.args:
                    dlr_url = updated_request.args['dlr-url'][0]
                else:
                    dlr_url = None
                if updated_request.args['dlr-level'][0] == '1':
                    dlr_level_text = 'SMS-C'
                elif updated_request.args['dlr-level'][0] == '2':
                    dlr_level_text = 'Terminal'
                else:
                    dlr_level_text = 'All'
                dlr_method = updated_request.args['dlr-method'][0]
            else:
                dlr_url = None
                dlr_level = 1
                dlr_level_text = 'No'
                dlr_method = None

            # Get number of PDUs to be sent (for billing purpose)
            _pdu = SubmitSmPDU
            submit_sm_count = 1
            while hasattr(_pdu, 'nextPdu'):
                _pdu = _pdu.nextPdu
                submit_sm_count += 1

            # Pre-sending submit_sm: Billing processing
            bill = route.getBillFor(user)
            self.log.debug(
                "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)"
                % (bill.bid, bill.getTotalAmounts(), submit_sm_count))
            charging_requirements = []
            u_balance = user.mt_credential.getQuota('balance')
            u_subsm_count = user.mt_credential.getQuota('submit_sm_count')
            if u_balance is not None and bill.getTotalAmounts() > 0:
                # Ensure user have enough balance to pay submit_sm and submit_sm_resp
                charging_requirements.append({
                    'condition':
                    bill.getTotalAmounts() * submit_sm_count <= u_balance,
                    'error_message':
                    'Not enough balance (%s) for charging: %s' %
                    (u_balance, bill.getTotalAmounts())
                })
            if u_subsm_count is not None:
                # Ensure user have enough submit_sm_count to to cover the bill action (decrement_submit_sm_count)
                charging_requirements.append({
                    'condition':
                    bill.getAction('decrement_submit_sm_count') *
                    submit_sm_count <= u_subsm_count,
                    'error_message':
                    'Not enough submit_sm_count (%s) for charging: %s' %
                    (u_subsm_count,
                     bill.getAction('decrement_submit_sm_count'))
                })

            if self.RouterPB.chargeUserForSubmitSms(
                    user, bill, submit_sm_count,
                    charging_requirements) is None:
                self.log.error(
                    'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)'
                    %
                    (user, bill.bid, bill.getTotalAmounts(), submit_sm_count))
                raise ChargingError(
                    'Cannot charge submit_sm, check RouterPB log file for details'
                )

            ########################################################
            # Send SubmitSmPDU through smpp client manager PB server
            self.log.debug(
                "Connector '%s' is set to be a route for this SubmitSmPDU" %
                routedConnector.cid)
            c = self.SMPPClientManagerPB.perspective_submit_sm(
                routedConnector.cid,
                SubmitSmPDU,
                priority,
                pickled=False,
                dlr_url=dlr_url,
                dlr_level=dlr_level,
                dlr_method=dlr_method,
                submit_sm_resp_bill=bill.getSubmitSmRespBill())

            # Build final response
            if not c.result:
                self.log.error('Failed to send SubmitSmPDU to [cid:%s]' %
                               routedConnector.cid)
                raise ServerError(
                    'Cannot send submit_sm, check SMPPClientManagerPB log file for details'
                )
            else:
                self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s' %
                               (routedConnector.cid, c.result))
                response = {'return': c.result, 'status': 200}
        except Exception, e:
            self.log.error("Error: %s" % e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}
        finally:
Esempio n. 28
0
class Send(Resource):
    isleaf = True

    def __init__(self, HTTPApiConfig, RouterPB, SMPPClientManagerPB, stats,
                 log, interceptorpb_client):
        Resource.__init__(self)

        self.SMPPClientManagerPB = SMPPClientManagerPB
        self.RouterPB = RouterPB
        self.stats = stats
        self.log = log
        self.interceptorpb_client = interceptorpb_client

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(
            long_content_max_parts=HTTPApiConfig.long_content_max_parts,
            long_content_split=HTTPApiConfig.long_content_split)

    @defer.inlineCallbacks
    def route_routable(self, updated_request):
        try:
            # Authentication
            user = self.RouterPB.authenticateUser(
                username=updated_request.args['username'][0],
                password=updated_request.args['password'][0])
            if user is None:
                self.stats.inc('auth_error_count')

                self.log.debug(
                    "Authentication failure for username:%s and password:%s",
                    updated_request.args['username'][0],
                    updated_request.args['password'][0])
                self.log.error("Authentication failure for username:%s",
                               updated_request.args['username'][0])
                raise AuthenticationError(
                    'Authentication failure for username:%s' %
                    updated_request.args['username'][0])

            # Update CnxStatus
            user.getCnxStatus().httpapi['connects_count'] += 1
            user.getCnxStatus().httpapi['submit_sm_request_count'] += 1
            user.getCnxStatus().httpapi['last_activity_at'] = datetime.now()

            # Build SubmitSmPDU
            SubmitSmPDU = self.opFactory.SubmitSM(
                source_addr=None if 'from' not in updated_request.args else
                updated_request.args['from'][0],
                destination_addr=updated_request.args['to'][0],
                short_message=updated_request.args['content'][0],
                data_coding=int(updated_request.args['coding'][0]))
            self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU)

            # Make Credential validation
            v = HttpAPICredentialValidator('Send',
                                           user,
                                           updated_request,
                                           submit_sm=SubmitSmPDU)
            v.validate()

            # Update SubmitSmPDU by default values from user MtMessagingCredential
            SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU)

            # Prepare for interception then routing
            routedConnector = None  # init
            routable = RoutableSubmitSm(SubmitSmPDU, user)
            self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable,
                           SubmitSmPDU)

            # Should we tag the routable ?
            tags = []
            if 'tags' in updated_request.args:
                tags = updated_request.args['tags'][0].split(',')
                for tag in tags:
                    routable.addTag(tag)
                    self.log.debug('Tagged routable %s: +%s', routable, tag)

            # Intercept
            interceptor = self.RouterPB.getMTInterceptionTable(
            ).getInterceptorFor(routable)
            if interceptor is not None:
                self.log.debug(
                    "RouterPB selected %s interceptor for this SubmitSmPDU",
                    interceptor)
                if self.interceptorpb_client is None:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not set !")
                    raise InterceptorNotSetError('InterceptorPB not set !')
                if not self.interceptorpb_client.isConnected:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not connected !")
                    raise InterceptorNotConnectedError(
                        'InterceptorPB not connected !')

                script = interceptor.getScript()
                self.log.debug("Interceptor script loaded: %s", script)

                # Run !
                r = yield self.interceptorpb_client.run_script(
                    script, routable)
                if isinstance(r, dict) and r['http_status'] != 200:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Interceptor script returned %s http_status error.',
                        r['http_status'])
                    raise InterceptorRunError(
                        code=r['http_status'],
                        message='Interception specific error code %s' %
                        r['http_status'])
                elif isinstance(r, str):
                    self.stats.inc('interceptor_count')
                    routable = pickle.loads(r)
                else:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got the following return: %s',
                        r)
                    raise InterceptorRunError(
                        message=
                        'Failed running interception script, check log for details'
                    )

            # Get the route
            route = self.RouterPB.getMTRoutingTable().getRouteFor(routable)
            if route is None:
                self.stats.inc('route_error_count')
                self.log.error(
                    "No route matched from user %s for SubmitSmPDU: %s", user,
                    routable.pdu)
                raise RouteNotFoundError("No route found")

            # Get connector from selected route
            self.log.debug("RouterPB selected %s route for this SubmitSmPDU",
                           route)
            routedConnector = route.getConnector()
            # Is it a failover route ? then check for a bound connector, otherwise don't route
            # The failover route requires at least one connector to be up, no message enqueuing will
            # occur otherwise.
            if repr(route) == 'FailoverMTRoute':
                self.log.debug(
                    'Selected route is a failover, will ensure connector is bound:'
                )
                while True:
                    c = self.SMPPClientManagerPB.perspective_connector_details(
                        routedConnector.cid)
                    if c:
                        self.log.debug('Connector [%s] is: %s',
                                       routedConnector.cid, c['session_state'])
                    else:
                        self.log.debug('Connector [%s] is not found',
                                       routedConnector.cid)

                    if c and c['session_state'][:6] == 'BOUND_':
                        # Choose this connector
                        break
                    else:
                        # Check next connector, None if no more connectors are available
                        routedConnector = route.getConnector()
                        if routedConnector is None:
                            break

            if routedConnector is None:
                self.stats.inc('route_error_count')
                self.log.error(
                    "Failover route has no bound connector to handle SubmitSmPDU: %s",
                    routable.pdu)
                raise ConnectorNotFoundError(
                    "Failover route has no bound connectors")

            # Re-update SubmitSmPDU with parameters from the route's connector
            connector_config = self.SMPPClientManagerPB.perspective_connector_config(
                routedConnector.cid)
            if connector_config:
                connector_config = pickle.loads(connector_config)
                routable = update_submit_sm_pdu(routable=routable,
                                                config=connector_config)

            # Set priority
            priority = 0
            if 'priority' in updated_request.args:
                priority = int(updated_request.args['priority'][0])
                routable.pdu.params['priority_flag'] = priority_flag_value_map[
                    priority]
            self.log.debug("SubmitSmPDU priority is set to %s", priority)

            # Set validity_period
            if 'validity-period' in updated_request.args:
                delta = timedelta(
                    minutes=int(updated_request.args['validity-period'][0]))
                routable.pdu.params['validity_period'] = datetime.today(
                ) + delta
                self.log.debug(
                    "SubmitSmPDU validity_period is set to %s (+%s minutes)",
                    routable.pdu.params['validity_period'],
                    updated_request.args['validity-period'][0])

            # Set DLR bit mask on the last pdu
            _last_pdu = routable.pdu
            while True:
                if hasattr(_last_pdu, 'nextPdu'):
                    _last_pdu = _last_pdu.nextPdu
                else:
                    break
            # DLR setting is clearly described in #107
            _last_pdu.params['registered_delivery'] = RegisteredDelivery(
                RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED)
            if updated_request.args['dlr'][0] == 'yes':
                _last_pdu.params['registered_delivery'] = RegisteredDelivery(
                    RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED)
                self.log.debug("SubmitSmPDU registered_delivery is set to %s",
                               str(_last_pdu.params['registered_delivery']))

                dlr_level = int(updated_request.args['dlr-level'][0])
                if 'dlr-url' in updated_request.args:
                    dlr_url = updated_request.args['dlr-url'][0]
                else:
                    dlr_url = None
                if updated_request.args['dlr-level'][0] == '1':
                    dlr_level_text = 'SMS-C'
                elif updated_request.args['dlr-level'][0] == '2':
                    dlr_level_text = 'Terminal'
                else:
                    dlr_level_text = 'All'
                dlr_method = updated_request.args['dlr-method'][0]
            else:
                dlr_url = None
                dlr_level = 0
                dlr_level_text = 'No'
                dlr_method = None

            # QoS throttling
            if user.mt_credential.getQuota(
                    'http_throughput') >= 0 and user.getCnxStatus(
                    ).httpapi['qos_last_submit_sm_at'] != 0:
                qos_throughput_second = 1 / float(
                    user.mt_credential.getQuota('http_throughput'))
                qos_throughput_ysecond_td = timedelta(
                    microseconds=qos_throughput_second * 1000000)
                qos_delay = datetime.now() - user.getCnxStatus(
                ).httpapi['qos_last_submit_sm_at']
                if qos_delay < qos_throughput_ysecond_td:
                    self.stats.inc('throughput_error_count')
                    self.log.error(
                        "QoS: submit_sm_event is faster (%s) than fixed throughput (%s), user:%s, rejecting message.",
                        qos_delay, qos_throughput_ysecond_td, user)

                    raise ThroughputExceededError("User throughput exceeded")
            user.getCnxStatus(
            ).httpapi['qos_last_submit_sm_at'] = datetime.now()

            # Get number of PDUs to be sent (for billing purpose)
            _pdu = routable.pdu
            submit_sm_count = 1
            while hasattr(_pdu, 'nextPdu'):
                _pdu = _pdu.nextPdu
                submit_sm_count += 1

            # Pre-sending submit_sm: Billing processing
            bill = route.getBillFor(user)
            self.log.debug(
                "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)",
                bill.bid, bill.getTotalAmounts(), submit_sm_count)
            charging_requirements = []
            u_balance = user.mt_credential.getQuota('balance')
            u_subsm_count = user.mt_credential.getQuota('submit_sm_count')
            if u_balance is not None and bill.getTotalAmounts() > 0:
                # Ensure user have enough balance to pay submit_sm and submit_sm_resp
                charging_requirements.append({
                    'condition':
                    bill.getTotalAmounts() * submit_sm_count <= u_balance,
                    'error_message':
                    'Not enough balance (%s) for charging: %s' %
                    (u_balance, bill.getTotalAmounts())
                })
            if u_subsm_count is not None:
                # Ensure user have enough submit_sm_count to to cover
                # the bill action (decrement_submit_sm_count)
                charging_requirements.append({
                    'condition':
                    bill.getAction('decrement_submit_sm_count') *
                    submit_sm_count <= u_subsm_count,
                    'error_message':
                    'Not enough submit_sm_count (%s) for charging: %s' %
                    (u_subsm_count,
                     bill.getAction('decrement_submit_sm_count'))
                })

            if self.RouterPB.chargeUserForSubmitSms(
                    user, bill, submit_sm_count,
                    charging_requirements) is None:
                self.stats.inc('charging_error_count')
                self.log.error(
                    'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)',
                    user, bill.bid, bill.getTotalAmounts(), submit_sm_count)
                raise ChargingError(
                    'Cannot charge submit_sm, check RouterPB log file for details'
                )

            ########################################################
            # Send SubmitSmPDU through smpp client manager PB server
            self.log.debug(
                "Connector '%s' is set to be a route for this SubmitSmPDU",
                routedConnector.cid)
            c = self.SMPPClientManagerPB.perspective_submit_sm(
                cid=routedConnector.cid,
                SubmitSmPDU=routable.pdu,
                submit_sm_bill=bill,
                priority=priority,
                pickled=False,
                dlr_url=dlr_url,
                dlr_level=dlr_level,
                dlr_method=dlr_method)

            # Build final response
            if not c.result:
                self.stats.inc('server_error_count')
                self.log.error('Failed to send SubmitSmPDU to [cid:%s]',
                               routedConnector.cid)
                raise ServerError(
                    'Cannot send submit_sm, check SMPPClientManagerPB log file for details'
                )
            else:
                self.stats.inc('success_count')
                self.stats.set('last_success_at', datetime.now())
                self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s',
                               routedConnector.cid, c.result)
                response = {'return': c.result, 'status': 200}
        except Exception, e:
            self.log.error("Error: %s", e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}
        finally:
Esempio n. 29
0
class SMPPClientSMListener(object):
    """
    This is a listener object instantiated for every new SMPP connection, it is responsible of handling
    SubmitSm, DeliverSm and SubmitSm PDUs for a given SMPP connection
    """
    def __init__(self,
                 config,
                 SMPPClientFactory,
                 amqpBroker,
                 redisClient,
                 RouterPB=None,
                 interceptorpb_client=None):
        self.config = config
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(
            self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.RouterPB = RouterPB
        self.interceptorpb_client = interceptorpb_client
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.submit_retrials = {}
        self.qosTimer = None

        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(
            self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format,
                                          self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

    def setSubmitSmQ(self, queue):
        self.log.debug('Setting a new submit_sm_q: %s', queue)
        self.submit_sm_q = queue

    def clearRejectTimer(self, msgid):
        if msgid in self.rejectTimers:
            timer = self.rejectTimers[msgid]
            if timer.active():
                timer.cancel()
            del self.rejectTimers[msgid]

    def clearRejectTimers(self):
        for msgid, timer in self.rejectTimers.items():
            if timer.active():
                timer.cancel()
            del self.rejectTimers[msgid]

    def clearQosTimer(self):
        if self.qosTimer is not None and self.qosTimer.called is False:
            self.qosTimer.cancel()
            self.qosTimer = None

    def clearAllTimers(self):
        self.clearQosTimer()
        self.clearRejectTimers()

    @defer.inlineCallbacks
    def rejectAndRequeueMessage(self, message, delay=True):
        msgid = message.content.properties['message-id']

        if delay:
            # Use configured requeue_delay or specific one
            if not isinstance(delay, bool):
                requeue_delay = delay
            else:
                requeue_delay = self.SMPPClientFactory.config.requeue_delay

            self.log.debug("Requeuing SubmitSmPDU[%s] in %s seconds", msgid,
                           requeue_delay)

            # Requeue the message with a delay
            timer = reactor.callLater(requeue_delay,
                                      self.rejectMessage,
                                      message=message,
                                      requeue=1)

            # If any, clear timer before setting a new one
            self.clearRejectTimer(msgid)

            self.rejectTimers[msgid] = timer
            defer.returnValue(timer)
        else:
            self.log.debug("Requeuing SubmitSmPDU[%s] without delay", msgid)
            yield self.rejectMessage(message, requeue=1)

    @defer.inlineCallbacks
    def rejectMessage(self, message, requeue=0):
        yield self.amqpBroker.chan.basic_reject(
            delivery_tag=message.delivery_tag, requeue=requeue)

    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    @defer.inlineCallbacks
    def submit_sm_callback(self, message):
        """This callback is a queue listener
        it is called whenever a message was consumed from queue
        c.f. test_amqp.ConsumeTestCase for use cases
        """
        msgid = None
        try:
            msgid = message.content.properties['message-id']
            SubmitSmPDU = pickle.loads(message.content.body)

            self.submit_sm_q.get().addCallback(
                self.submit_sm_callback).addErrback(self.submit_sm_errback)

            self.log.debug(
                "Callbacked a submit_sm with a SubmitSmPDU[%s] (?): %s", msgid,
                SubmitSmPDU)

            # Update submit_sm retrial tracker
            if msgid in self.submit_retrials:
                self.submit_retrials[msgid] += 1
            else:
                self.submit_retrials[msgid] = 1

            if self.qos_last_submit_sm_at is None:
                self.qos_last_submit_sm_at = datetime(1970, 1, 1)

            if self.SMPPClientFactory.config.submit_sm_throughput > 0:
                # QoS throttling
                qos_throughput_second = 1 / float(
                    self.SMPPClientFactory.config.submit_sm_throughput)
                qos_throughput_ysecond_td = timedelta(
                    microseconds=qos_throughput_second * 1000000)
                qos_delay = datetime.now() - self.qos_last_submit_sm_at
                if qos_delay < qos_throughput_ysecond_td:
                    qos_slow_down = float((qos_throughput_ysecond_td -
                                           qos_delay).microseconds) / 1000000
                    # We're faster than submit_sm_throughput,
                    # slow down before taking a new message from the queue
                    self.log.debug(
                        "QoS: submit_sm_callback faster (%s) than throughput (%s), slowing down %ss (requeuing).",
                        qos_delay, qos_throughput_ysecond_td, qos_slow_down)

                    # Relaunch queue callbacking after qos_slow_down seconds
                    # self.qosTimer = task.deferLater(reactor, qos_slow_down, self.submit_sm_q.get)
                    # self.qosTimer.addCallback(self.submit_sm_callback).addErrback(self.submit_sm_errback)
                    # Requeue the message
                    yield self.rejectAndRequeueMessage(message,
                                                       delay=qos_slow_down)
                    defer.returnValue(False)

                self.qos_last_submit_sm_at = datetime.now()

            # Verify if message is a SubmitSm PDU
            if isinstance(SubmitSmPDU, SubmitSM) is False:
                self.log.error(
                    "Received object[%s] is not an instance of SubmitSm: discarding this unknown object from queue",
                    msgid)
                yield self.rejectMessage(message)
                defer.returnValue(False)
            # If the message has expired in the queue
            if 'headers' in message.content.properties and 'expiration' in message.content.properties[
                    'headers']:
                expiration_datetime = parser.parse(
                    message.content.properties['headers']['expiration'])
                if expiration_datetime < datetime.now():
                    self.log.info(
                        "Discarding expired message[%s]: expiration is %s",
                        msgid, expiration_datetime)
                    yield self.rejectMessage(message)
                    defer.returnValue(False)
            # SMPP Client should be already connected
            if self.SMPPClientFactory.smpp is None:
                created_at = parser.parse(
                    message.content.properties['headers']['created_at'])
                msgAge = datetime.now() - created_at
                if msgAge.seconds > self.config.submit_max_age_smppc_not_ready:
                    self.log.error(
                        "SMPPC [cid:%s] is not connected: Discarding (#%s) SubmitSmPDU[%s], over-aged %s seconds.",
                        self.SMPPClientFactory.config.id,
                        self.submit_retrials[msgid], msgid, msgAge.seconds)
                    yield self.rejectMessage(message)
                    defer.returnValue(False)
                else:
                    if self.config.submit_retrial_delay_smppc_not_ready:
                        delay_str = ' with delay %s seconds' % self.config.submit_retrial_delay_smppc_not_ready
                    else:
                        delay_str = ''
                    self.log.error(
                        "SMPPC [cid:%s] is not connected: Requeuing (#%s) SubmitSmPDU[%s]%s, aged %s seconds.",
                        self.SMPPClientFactory.config.id,
                        self.submit_retrials[msgid], msgid, delay_str,
                        msgAge.seconds)
                    yield self.rejectAndRequeueMessage(
                        message,
                        delay=self.config.submit_retrial_delay_smppc_not_ready)
                    defer.returnValue(False)
            # SMPP Client should be already bound as transceiver or transmitter
            if self.SMPPClientFactory.smpp.isBound() is False:
                created_at = parser.parse(
                    message.content.properties['headers']['created_at'])
                msgAge = datetime.now() - created_at
                if msgAge.seconds > self.config.submit_max_age_smppc_not_ready:
                    self.log.error(
                        "SMPPC [cid:%s] is not bound: Discarding (#%s) SubmitSmPDU[%s], over-aged %s seconds.",
                        self.SMPPClientFactory.config.id,
                        self.submit_retrials[msgid], msgid, msgAge.seconds)
                    yield self.rejectMessage(message)
                    defer.returnValue(False)
                else:
                    if self.config.submit_retrial_delay_smppc_not_ready:
                        delay_str = ' with delay %s seconds' % self.config.submit_retrial_delay_smppc_not_ready
                    else:
                        delay_str = ''
                    self.log.error(
                        "SMPPC [cid:%s] is not bound: Requeuing (#%s) SubmitSmPDU[%s]%s, aged %s seconds.",
                        self.SMPPClientFactory.config.id,
                        self.submit_retrials[msgid], msgid, delay_str, msgAge)
                    yield self.rejectAndRequeueMessage(
                        message,
                        delay=self.config.submit_retrial_delay_smppc_not_ready)
                    defer.returnValue(False)

            # Finally: send the sms !
            self.log.debug(
                "Sending SubmitSmPDU[%s] through SMPPClientFactory [cid:%s]",
                msgid, self.SMPPClientFactory.config.id)
            d = self.SMPPClientFactory.smpp.sendDataRequest(SubmitSmPDU)
            d.addCallback(self.submit_sm_resp_event, message)
            yield d
        except SMPPRequestTimoutError:
            self.log.error(
                "SubmitSmPDU[%s] request timed out through [cid:%s], message requeued.",
                msgid, self.SMPPClientFactory.config.id)
            self.rejectAndRequeueMessage(message)
            defer.returnValue(False)
        except LongSubmitSmTransactionError as e:
            self.log.error(
                "Long SubmitSmPDU[%s] error in [cid:%s], message requeued: %s",
                msgid, self.SMPPClientFactory.config.id, e.message)
            self.rejectAndRequeueMessage(message)
            defer.returnValue(False)
        except Exception as e:
            self.log.critical(
                "Rejecting SubmitSmPDU[%s] through [cid:%s] for an unknown error (%s): %s",
                msgid, self.SMPPClientFactory.config.id, type(e), e)
            self.rejectMessage(message)
            defer.returnValue(False)

    @defer.inlineCallbacks
    def submit_sm_resp_event(self, r, amqpMessage):
        msgid = amqpMessage.content.properties['message-id']
        total_bill_amount = None
        will_be_retried = False

        try:
            submit_sm_resp_bill = pickle.loads(
                amqpMessage.content.properties['headers']
                ['submit_sm_bill']).getSubmitSmRespBill()

            if r.response.status == CommandStatus.ESME_ROK:
                # No more retrials !
                del self.submit_retrials[msgid]

                # Get bill information
                total_bill_amount = 0.0
                if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts(
                ) > 0:
                    total_bill_amount = submit_sm_resp_bill.getTotalAmounts()

                # UDH is set ?
                UDHI_INDICATOR_SET = False
                if hasattr(r.request.params['esm_class'], 'gsmFeatures'):
                    for gsmFeature in r.request.params[
                            'esm_class'].gsmFeatures:
                        if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                            UDHI_INDICATOR_SET = True
                            break

                # What type of splitting ?
                splitMethod = None
                if 'sar_msg_ref_num' in r.request.params:
                    splitMethod = 'sar'
                elif UDHI_INDICATOR_SET and r.request.params[
                        'short_message'][:3] == '\x05\x00\x03':
                    splitMethod = 'udh'

                # Concatenate short_message
                if splitMethod is not None:
                    _pdu = r.request
                    if splitMethod == 'sar':
                        short_message = _pdu.params['short_message']
                    else:
                        short_message = _pdu.params['short_message'][6:]

                    while hasattr(_pdu, 'nextPdu'):
                        _pdu = _pdu.nextPdu
                        if splitMethod == 'sar':
                            short_message += _pdu.params['short_message']
                        else:
                            short_message += _pdu.params['short_message'][6:]

                        # Increase bill amount for each submit_sm_resp
                        if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts(
                        ) > 0:
                            total_bill_amount += submit_sm_resp_bill.getTotalAmounts(
                            )
                else:
                    short_message = r.request.params['short_message']

                # Do not log text for privacy reasons
                # Added in #691
                if self.config.log_privacy:
                    logged_content = '** %s byte content **' % len(
                        short_message)
                else:
                    logged_content = '%r' % short_message

                self.log.info(
                    "SMS-MT [cid:%s] [queue-msgid:%s] [smpp-msgid:%s] [status:%s] [prio:%s] [dlr:%s] [validity:%s] \
[from:%s] [to:%s] [content:%s]", self.SMPPClientFactory.config.id, msgid,
                    r.response.params['message_id'], r.response.status,
                    amqpMessage.content.properties['priority'],
                    r.request.params['registered_delivery'].receipt, 'none' if
                    ('headers' not in amqpMessage.content.properties
                     or 'expiration'
                     not in amqpMessage.content.properties['headers']) else
                    amqpMessage.content.properties['headers']['expiration'],
                    r.request.params['source_addr'],
                    r.request.params['destination_addr'], logged_content)
            else:
                # Message must be retried ?
                if str(r.response.status) in self.config.submit_error_retrial:
                    retrial = self.config.submit_error_retrial[str(
                        r.response.status)]

                    # Still have some retries to go ?
                    if self.submit_retrials[msgid] < retrial['count']:
                        # Requeue the message for later redelivery
                        yield self.rejectAndRequeueMessage(
                            amqpMessage, delay=retrial['delay'])
                        will_be_retried = True
                    else:
                        # Prevent this list from over-growing
                        del self.submit_retrials[msgid]

                # Do not log text for privacy reasons
                # Added in #691
                if self.config.log_privacy:
                    logged_content = '** %s byte content **' % len(
                        r.request.params['short_message'])
                else:
                    logged_content = '%r' % r.request.params['short_message']

                # Log the message
                self.log.info(
                    "SMS-MT [cid:%s] [queue-msgid:%s] [status:ERROR/%s] [retry:%s] [prio:%s] [dlr:%s] [validity:%s] \
[from:%s] [to:%s] [content:%s]", self.SMPPClientFactory.config.id, msgid,
                    r.response.status, will_be_retried,
                    amqpMessage.content.properties['priority'],
                    r.request.params['registered_delivery'].receipt, 'none' if
                    ('headers' not in amqpMessage.content.properties
                     or 'expiration'
                     not in amqpMessage.content.properties['headers']) else
                    amqpMessage.content.properties['headers']['expiration'],
                    r.request.params['source_addr'],
                    r.request.params['destination_addr'], logged_content)

            # It is a final submit_sm_resp !
            if not will_be_retried:
                # Cancel any mapped rejectTimer to this message
                # (in case this message was rejected in the past)
                self.clearRejectTimer(msgid)
                self.log.debug(
                    "ACKing amqpMessage [%s] having routing_key [%s]", msgid,
                    amqpMessage.routing_key)
                # ACK the message in queue, this will remove it from the queue
                yield self.ackMessage(amqpMessage)

            # Send DLR to DLRLookup
            if r.response.status == CommandStatus.ESME_ROK:
                dlr = DLR(pdu_type=r.response.id,
                          msgid=msgid,
                          status=r.response.status,
                          smpp_msgid=r.response.params['message_id'])
            else:
                dlr = DLR(pdu_type=r.response.id,
                          msgid=msgid,
                          status=r.response.status)
            yield self.amqpBroker.publish(exchange='messaging',
                                          routing_key='dlr.submit_sm_resp',
                                          content=dlr)

            # Bill will be charged by bill_request.submit_sm_resp.UID queue consumer
            if total_bill_amount > 0:
                pubQueueName = 'bill_request.submit_sm_resp.%s' % submit_sm_resp_bill.user.uid
                content = SubmitSmRespBillContent(submit_sm_resp_bill.bid,
                                                  submit_sm_resp_bill.user.uid,
                                                  total_bill_amount)
                self.log.debug(
                    "Requesting a SubmitSmRespBillContent from a bill [bid:%s] with routing_key[%s]: %s",
                    submit_sm_resp_bill.bid, pubQueueName, total_bill_amount)
                yield self.amqpBroker.publish(exchange='billing',
                                              routing_key=pubQueueName,
                                              content=content)

            if self.config.publish_submit_sm_resp:
                # Send back submit_sm_resp to submit.sm.resp.CID queue
                # There's no actual listeners on this queue, it can be used to
                # track submit_sm_resp messages from a 3rd party app
                content = SubmitSmRespContent(
                    r.response, msgid, pickleProtocol=self.pickleProtocol)
                self.log.debug(
                    "Sending back SubmitSmRespContent[%s] with routing_key[%s]",
                    msgid, amqpMessage.content.properties['reply-to'])
                yield self.amqpBroker.publish(
                    exchange='messaging',
                    routing_key=amqpMessage.content.properties['reply-to'],
                    content=content)
        except Exception as e:
            self.log.error(
                '(%s) while handling submit_sm_resp pdu for msgid:%s: %s',
                type(e), msgid, e)
        else:
            if will_be_retried:
                defer.returnValue(False)

    def submit_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didn't find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) is None:
            # @todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occurred inside submit_sm_callback
            # - the qosTimer has been cancelled (self.clearQosTimer())
            try:
                error.raiseException()
            except Exception as e:
                self.log.error("Error in submit_sm_errback (%s): %s", type(e),
                               e)

    @defer.inlineCallbacks
    def concatDeliverSMs(self, HSetReturn, hashKey, splitMethod,
                         total_segments, msg_ref_num, segment_seqnum):
        if HSetReturn == 0:
            self.log.warn(
                'This hashKey %s already exists, will not reset it !', hashKey)
            return

        # @TODO: longDeliverSm part expiry must be configurable
        yield self.redisClient.expire(hashKey, 300)

        # This is the last part
        if segment_seqnum == total_segments:
            hvals = yield self.redisClient.hvals(hashKey)
            if len(hvals) != total_segments:
                self.log.warn(
                    'Received the last part (msg_ref_num:%s) and did not find all parts in redis, data lost !',
                    msg_ref_num)
                return

            # Get PDUs
            pdus = {}
            for pickledValue in hvals:
                value = pickle.loads(pickledValue)

                pdus[value['segment_seqnum']] = value['pdu']

            # Where is the message content to be found ?
            if 'short_message' in pdus[1].params:
                msg_content_key = 'short_message'
            elif 'message_payload' in pdus[1].params:
                msg_content_key = 'message_payload'
            else:
                self.log.warn(
                    'Cannot find message content in first pdu params: %s',
                    pdus[1].params)
                return

            # Build concat_message_content
            concat_message_content = ''
            for i in range(total_segments):
                if splitMethod == 'sar':
                    concat_message_content += pdus[i +
                                                   1].params[msg_content_key]
                else:
                    concat_message_content += pdus[
                        i + 1].params[msg_content_key][6:]

            # Build the final pdu and return it back to deliver_sm_event
            pdu = pdus[1]  # Take the first part as a base of work
            # 1. Remove message splitting information from pdu
            if splitMethod == 'sar':
                del pdu.params['sar_segment_seqnum']
                del pdu.params['sar_total_segments']
                del pdu.params['sar_msg_ref_num']
            else:
                pdu.params['esm_class'] = None
            # 2. Set the new concat_message_content
            pdu.params[msg_content_key] = concat_message_content

            routable = RoutableDeliverSm(
                pdu, Connector(self.SMPPClientFactory.config.id))
            yield self.deliver_sm_event_post_interception(routable=routable,
                                                          smpp=None,
                                                          concatenated=True)

    def code_dlr_msgid(self, pdu):
        """Code the dlr msg id accordingly to SMPPc's dlr_msg_id_bases value"""

        try:
            if isinstance(pdu, DeliverSM):
                if self.SMPPClientFactory.config.dlr_msg_id_bases == 1:
                    ret = ('%x' % int(pdu.dlr['id'])).upper().lstrip('0')
                elif self.SMPPClientFactory.config.dlr_msg_id_bases == 2:
                    ret = int(str(pdu.dlr['id']), 16)
                else:
                    ret = str(pdu.dlr['id']).upper().lstrip('0')
            else:
                # TODO: code dlr for submit_sm_resp maybe ? TBC
                ret = str(pdu.dlr['id']).upper().lstrip('0')
        except Exception as e:
            self.log.error(
                'code_dlr_msgid, cannot code msgid [%s] with dlr_msg_id_bases:%s',
                pdu.dlr['id'], self.SMPPClientFactory.config.dlr_msg_id_bases)
            self.log.error('code_dlr_msgid, error details: %s', e)
            ret = str(pdu.dlr['id']).upper().lstrip('0')

        self.log.debug('code_dlr_msgid: %s coded to %s', pdu.dlr['id'], ret)
        return ret

    def deliver_sm_event_interceptor(self, smpp, pdu):
        self.log.debug('Intercepting deliver_sm event in smppc %s',
                       self.SMPPClientFactory.config.id)

        if self.RouterPB is None:
            self.log.error(
                '(deliver_sm_event_interceptor/%s) RouterPB not set: deliver_sm will not be routed',
                self.SMPPClientFactory.config.id)
            return

        # Prepare for interception
        # this is a temporary routable instance to be used in interception
        routable = RoutableDeliverSm(
            pdu, Connector(self.SMPPClientFactory.config.id))

        # Interception inline
        # @TODO: make Interception in a thread, just like httpapi interception
        interceptor = self.RouterPB.getMOInterceptionTable().getInterceptorFor(
            routable)
        if interceptor is not None:
            self.log.debug(
                "RouterPB selected %s interceptor for this DeliverSmPDU",
                interceptor)
            if self.interceptorpb_client is None:
                smpp.factory.stats.inc('interceptor_error_count')
                self.log.error("InterceptorPB not set !")
                raise InterceptorNotSetError('InterceptorPB not set !')
            if not self.interceptorpb_client.isConnected:
                smpp.factory.stats.inc('interceptor_error_count')
                self.log.error("InterceptorPB not connected !")
                raise InterceptorNotConnectedError(
                    'InterceptorPB not connected !')

            script = interceptor.getScript()
            self.log.debug("Interceptor script loaded: %s", script)

            # Run !
            d = self.interceptorpb_client.run_script(script, routable)
            d.addCallback(self.deliver_sm_event_post_interception,
                          routable=routable,
                          smpp=smpp)
            d.addErrback(self.deliver_sm_event_post_interception)
            return d
        else:
            return self.deliver_sm_event_post_interception(routable=routable,
                                                           smpp=smpp)

    @defer.inlineCallbacks
    def deliver_sm_event_post_interception(self, *args, **kw):
        """This event is called whenever a deliver_sm pdu is received through a SMPPc
        It will hand the pdu to the router or a dlr thrower (depending if its a DLR or not).

        Note: this event will catch data_sm pdus as well
        """

        try:
            # Control args
            if 'smpp' not in kw or 'routable' not in kw:
                self.log.error(
                    'deliver_sm_event_post_interception missing arguments after interception: %s',
                    kw)
                raise InterceptorRunError(
                    'deliver_sm_event_post_interception missing arguments after interception'
                )

            # Set defaults
            smpp = kw['smpp']
            routable = kw['routable']

            if 'concatenated' in kw:
                concatenated = kw['concatenated']
            else:
                concatenated = False

            # Get message_content
            if 'short_message' in routable.pdu.params and len(
                    routable.pdu.params['short_message']) > 0:
                message_content = routable.pdu.params['short_message']
            elif 'message_payload' in routable.pdu.params:
                message_content = routable.pdu.params['message_payload']
            elif 'short_message' in routable.pdu.params:
                message_content = routable.pdu.params['short_message']
            else:
                message_content = None

            # Post interception:
            if len(args) == 1:
                if isinstance(args[0], bool) and not args[0]:
                    smpp.factory.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got a False return.'
                    )
                    raise InterceptorRunError(
                        'Failed running interception script, check log for details'
                    )
                elif isinstance(args[0], dict) and args[0]['smpp_status'] > 0:
                    smpp.factory.stats.inc('interceptor_error_count')
                    self.log.info(
                        'Interceptor script returned %s smpp_status error.',
                        args[0]['smpp_status'])
                    raise DeliverSmInterceptionError(
                        code=args[0]['smpp_status'])
                elif isinstance(args[0], str):
                    smpp.factory.stats.inc('interceptor_count')
                    routable = pickle.loads(args[0])
                else:
                    smpp.factory.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got the following return: %s',
                        args[0])
                    raise InterceptorRunError(
                        'Failed running interception script, got the following return: %s'
                        % args[0])

            self.log.debug(
                'Handling deliver_sm_event_post_interception event for smppc: %s',
                self.SMPPClientFactory.config.id)

            routable.pdu.dlr = self.SMPPOperationFactory.isDeliveryReceipt(
                routable.pdu)
            content = DeliverSmContent(routable,
                                       self.SMPPClientFactory.config.id,
                                       pickleProtocol=self.pickleProtocol,
                                       concatenated=concatenated)
            msgid = content.properties['message-id']

            if routable.pdu.dlr is None:
                # We have a SMS-MO

                # UDH is set ?
                UDHI_INDICATOR_SET = False
                if 'esm_class' in routable.pdu.params and hasattr(
                        routable.pdu.params['esm_class'], 'gsmFeatures'):
                    for gsmFeature in routable.pdu.params[
                            'esm_class'].gsmFeatures:
                        if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                            UDHI_INDICATOR_SET = True
                            break

                not_class2 = True
                if 'data_coding' in routable.pdu.params:
                    dcs = routable.pdu.params['data_coding']
                    if (str(dcs.scheme)
                            == 'GSM_MESSAGE_CLASS') and (dcs.schemeData
                                                         is not None):
                        not_class2 = (str(dcs.schemeData.msgClass) !=
                                      'CLASS_2')

                splitMethod = None
                # Is it a part of a long message ?
                if 'sar_msg_ref_num' in routable.pdu.params:
                    splitMethod = 'sar'
                    total_segments = routable.pdu.params['sar_total_segments']
                    segment_seqnum = routable.pdu.params['sar_segment_seqnum']
                    msg_ref_num = routable.pdu.params['sar_msg_ref_num']
                    self.log.debug(
                        'Received SMS-MO part [queue-msgid:%s] using SAR: ttl_segments=%s, segment_sn=%s, msgref=%s',
                        msgid, total_segments, segment_seqnum, msg_ref_num)
                elif UDHI_INDICATOR_SET and not_class2 and message_content[:
                                                                           3] == '\x05\x00\x03':
                    splitMethod = 'udh'
                    total_segments = struct.unpack('!B', message_content[4])[0]
                    segment_seqnum = struct.unpack('!B', message_content[5])[0]
                    msg_ref_num = struct.unpack('!B', message_content[3])[0]
                    self.log.debug(
                        'Received SMS-MO part [queue-msgid:%s] using UDH: ttl_segments=%s, segment_sn=%s, msgref=%s',
                        msgid, total_segments, segment_seqnum, msg_ref_num)

                if splitMethod is None:
                    # It's a simple short message or a part of a concatenated message
                    routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                    self.log.debug(
                        "Publishing DeliverSmContent[%s] with routing_key[%s]",
                        msgid, routing_key)
                    yield self.amqpBroker.publish(exchange='messaging',
                                                  routing_key=routing_key,
                                                  content=content)

                    # Get values from data_sm or deliver_sm
                    priority_flag = None
                    if 'priority_flag' in routable.pdu.params:
                        priority_flag = routable.pdu.params['priority_flag']
                    validity_period = None
                    if 'validity_period' in routable.pdu.params:
                        validity_period = routable.pdu.params[
                            'validity_period']

                    # Do not log text for privacy reasons
                    # Added in #691
                    if self.config.log_privacy:
                        logged_content = '** %s byte content **' % len(
                            message_content)
                    else:
                        logged_content = '%r' % message_content

                    self.log.info(
                        "SMS-MO [cid:%s] [queue-msgid:%s] [status:%s] [prio:%s] [validity:%s] [from:%s] [to:%s] \
[content:%s]", self.SMPPClientFactory.config.id, msgid, routable.pdu.status,
                        priority_flag, validity_period,
                        routable.pdu.params['source_addr'],
                        routable.pdu.params['destination_addr'],
                        logged_content)
                else:
                    # Long message part received
                    if self.redisClient is None:
                        self.log.critical(
                            'Invalid RC found while receiving part of long DeliverSm [queue-msgid:%s], MSG IS LOST !',
                            msgid)
                    else:
                        # Save it to redis
                        hashKey = "longDeliverSm:%s:%s:%s" % (
                            self.SMPPClientFactory.config.id, msg_ref_num,
                            routable.pdu.params['destination_addr'])
                        hashValues = {
                            'pdu': routable.pdu,
                            'total_segments': total_segments,
                            'msg_ref_num': msg_ref_num,
                            'segment_seqnum': segment_seqnum
                        }
                        yield self.redisClient.hset(
                            hashKey, segment_seqnum,
                            pickle.dumps(hashValues,
                                         self.pickleProtocol)).addCallback(
                                             self.concatDeliverSMs, hashKey,
                                             splitMethod, total_segments,
                                             msg_ref_num, segment_seqnum)

                        self.log.info(
                            "DeliverSmContent[%s] is part of long msg of (%s), will be enqueued after concatenation.",
                            msgid, total_segments)

                        # Flag it as "will_be_concatenated" and publish it to router
                        routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                        self.log.debug(
                            "Publishing DeliverSmContent[%s](flagged:wbc) with routing_key[%s]",
                            msgid, routing_key)
                        content.properties['headers'][
                            'will_be_concatenated'] = True
                        yield self.amqpBroker.publish(exchange='messaging',
                                                      routing_key=routing_key,
                                                      content=content)
            else:
                # This is a DLR !
                # Send DLR to DLRLookup
                yield self.amqpBroker.publish(
                    exchange='messaging',
                    routing_key='dlr.deliver_sm',
                    content=DLR(pdu_type=routable.pdu.id,
                                msgid=self.code_dlr_msgid(routable.pdu),
                                status=routable.pdu.dlr['stat'],
                                cid=self.SMPPClientFactory.config.id,
                                dlr_details=routable.pdu.dlr))
        except (InterceptorRunError, DeliverSmInterceptionError) as e:
            # Do not log text for privacy reasons
            # Added in #691
            if self.config.log_privacy:
                logged_content = '** %s byte content **' % len(message_content)
            else:
                logged_content = '%r' % message_content

            self.log.info(
                "SMS-MO [cid:%s] [i-status:%s] [from:%s] [to:%s] [content:%s]",
                self.SMPPClientFactory.config.id, e.status,
                routable.pdu.params['source_addr'],
                routable.pdu.params['destination_addr'], logged_content)

            # Known exception handling
            defer.returnValue(DataHandlerResponse(status=e.status))
        except Exception as e:
            # Unknown exception handling
            self.log.critical('Got an unknown exception (%s): %s', type(e), e)
            defer.returnValue(
                DataHandlerResponse(status=CommandStatus.ESME_RUNKNOWNERR))
Esempio n. 30
0
class Send(Resource):
    isleaf = True

    def __init__(self, HTTPApiConfig, RouterPB, SMPPClientManagerPB, stats,
                 log, interceptorpb_client):
        Resource.__init__(self)

        self.SMPPClientManagerPB = SMPPClientManagerPB
        self.RouterPB = RouterPB
        self.stats = stats
        self.log = log
        self.interceptorpb_client = interceptorpb_client

        # opFactory is initiated with a dummy SMPPClientConfig used for building SubmitSm only
        self.opFactory = SMPPOperationFactory(
            long_content_max_parts=HTTPApiConfig.long_content_max_parts,
            long_content_split=HTTPApiConfig.long_content_split)

    @defer.inlineCallbacks
    def route_routable(self, updated_request):
        try:
            # Do we have a hex-content ?
            if 'hex-content' not in updated_request.args:
                # Convert utf8 to GSM 03.38
                if updated_request.args['coding'][0] == '0':
                    short_message = gsm_encode(
                        updated_request.args['content'][0].decode('utf-8'))
                else:
                    # Otherwise forward it as is
                    short_message = updated_request.args['content'][0]
            else:
                # Otherwise convert hex to bin
                short_message = hex2bin(updated_request.args['hex-content'][0])

            # Authentication
            user = self.RouterPB.authenticateUser(
                username=updated_request.args['username'][0],
                password=updated_request.args['password'][0])
            if user is None:
                self.stats.inc('auth_error_count')

                self.log.debug(
                    "Authentication failure for username:%s and password:%s",
                    updated_request.args['username'][0],
                    updated_request.args['password'][0])
                self.log.error("Authentication failure for username:%s",
                               updated_request.args['username'][0])
                raise AuthenticationError(
                    'Authentication failure for username:%s' %
                    updated_request.args['username'][0])

            # Update CnxStatus
            user.getCnxStatus().httpapi['connects_count'] += 1
            user.getCnxStatus().httpapi['submit_sm_request_count'] += 1
            user.getCnxStatus().httpapi['last_activity_at'] = datetime.now()

            # Build SubmitSmPDU
            SubmitSmPDU = self.opFactory.SubmitSM(
                source_addr=None if 'from' not in updated_request.args else
                updated_request.args['from'][0],
                destination_addr=updated_request.args['to'][0],
                short_message=short_message,
                data_coding=int(updated_request.args['coding'][0]),
                custom_tlvs=updated_request.args['custom_tlvs'][0])
            self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU)

            # Make Credential validation
            v = HttpAPICredentialValidator('Send',
                                           user,
                                           updated_request,
                                           submit_sm=SubmitSmPDU)
            v.validate()

            # Update SubmitSmPDU by default values from user MtMessagingCredential
            SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU)

            # Prepare for interception then routing
            routedConnector = None  # init
            routable = RoutableSubmitSm(SubmitSmPDU, user)
            self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable,
                           SubmitSmPDU)

            # Should we tag the routable ?
            tags = []
            if 'tags' in updated_request.args:
                tags = updated_request.args['tags'][0].split(',')
                for tag in tags:
                    routable.addTag(tag)
                    self.log.debug('Tagged routable %s: +%s', routable, tag)

            # Intercept
            interceptor = self.RouterPB.getMTInterceptionTable(
            ).getInterceptorFor(routable)
            if interceptor is not None:
                self.log.debug(
                    "RouterPB selected %s interceptor for this SubmitSmPDU",
                    interceptor)
                if self.interceptorpb_client is None:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not set !")
                    raise InterceptorNotSetError('InterceptorPB not set !')
                if not self.interceptorpb_client.isConnected:
                    self.stats.inc('interceptor_error_count')
                    self.log.error("InterceptorPB not connected !")
                    raise InterceptorNotConnectedError(
                        'InterceptorPB not connected !')

                script = interceptor.getScript()
                self.log.debug("Interceptor script loaded: %s", script)

                # Run !
                r = yield self.interceptorpb_client.run_script(
                    script, routable)
                if isinstance(r, dict) and r['http_status'] != 200:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Interceptor script returned %s http_status error.',
                        r['http_status'])
                    raise InterceptorRunError(
                        code=r['http_status'],
                        message='Interception specific error code %s' %
                        r['http_status'])
                elif isinstance(r, str):
                    self.stats.inc('interceptor_count')
                    routable = pickle.loads(r)
                else:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Failed running interception script, got the following return: %s',
                        r)
                    raise InterceptorRunError(
                        message=
                        'Failed running interception script, check log for details'
                    )

            # Get the route
            route = self.RouterPB.getMTRoutingTable().getRouteFor(routable)
            if route is None:
                self.stats.inc('route_error_count')
                self.log.error(
                    "No route matched from user %s for SubmitSmPDU: %s", user,
                    routable.pdu)
                raise RouteNotFoundError("No route found")

            # Get connector from selected route
            self.log.debug("RouterPB selected %s route for this SubmitSmPDU",
                           route)
            routedConnector = route.getConnector()
            # Is it a failover route ? then check for a bound connector, otherwise don't route
            # The failover route requires at least one connector to be up, no message enqueuing will
            # occur otherwise.
            if repr(route) == 'FailoverMTRoute':
                self.log.debug(
                    'Selected route is a failover, will ensure connector is bound:'
                )
                while True:
                    c = self.SMPPClientManagerPB.perspective_connector_details(
                        routedConnector.cid)
                    if c:
                        self.log.debug('Connector [%s] is: %s',
                                       routedConnector.cid, c['session_state'])
                    else:
                        self.log.debug('Connector [%s] is not found',
                                       routedConnector.cid)

                    if c and c['session_state'][:6] == 'BOUND_':
                        # Choose this connector
                        break
                    else:
                        # Check next connector, None if no more connectors are available
                        routedConnector = route.getConnector()
                        if routedConnector is None:
                            break

            if routedConnector is None:
                self.stats.inc('route_error_count')
                self.log.error(
                    "Failover route has no bound connector to handle SubmitSmPDU: %s",
                    routable.pdu)
                raise ConnectorNotFoundError(
                    "Failover route has no bound connectors")

            # Re-update SubmitSmPDU with parameters from the route's connector
            connector_config = self.SMPPClientManagerPB.perspective_connector_config(
                routedConnector.cid)
            if connector_config:
                connector_config = pickle.loads(connector_config)
                routable = update_submit_sm_pdu(routable=routable,
                                                config=connector_config)

            # Set priority
            priority = 0
            if 'priority' in updated_request.args:
                priority = int(updated_request.args['priority'][0])
                routable.pdu.params['priority_flag'] = priority_flag_value_map[
                    priority]
            self.log.debug("SubmitSmPDU priority is set to %s", priority)

            # Set schedule_delivery_time
            if 'sdt' in updated_request.args:
                routable.pdu.params['schedule_delivery_time'] = parse(
                    updated_request.args['sdt'][0])
                self.log.debug(
                    "SubmitSmPDU schedule_delivery_time is set to %s (%s)",
                    routable.pdu.params['schedule_delivery_time'],
                    updated_request.args['sdt'][0])

            # Set validity_period
            if 'validity-period' in updated_request.args:
                delta = timedelta(
                    minutes=int(updated_request.args['validity-period'][0]))
                routable.pdu.params['validity_period'] = datetime.today(
                ) + delta
                self.log.debug(
                    "SubmitSmPDU validity_period is set to %s (+%s minutes)",
                    routable.pdu.params['validity_period'],
                    updated_request.args['validity-period'][0])

            # Set DLR bit mask on the last pdu
            _last_pdu = routable.pdu
            while True:
                if hasattr(_last_pdu, 'nextPdu'):
                    _last_pdu = _last_pdu.nextPdu
                else:
                    break
            # DLR setting is clearly described in #107
            _last_pdu.params['registered_delivery'] = RegisteredDelivery(
                RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED)
            if updated_request.args['dlr'][0] == 'yes':
                _last_pdu.params['registered_delivery'] = RegisteredDelivery(
                    RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED)
                self.log.debug("SubmitSmPDU registered_delivery is set to %s",
                               str(_last_pdu.params['registered_delivery']))

                dlr_level = int(updated_request.args['dlr-level'][0])
                if 'dlr-url' in updated_request.args:
                    dlr_url = updated_request.args['dlr-url'][0]
                else:
                    dlr_url = None
                if updated_request.args['dlr-level'][0] == '1':
                    dlr_level_text = 'SMS-C'
                elif updated_request.args['dlr-level'][0] == '2':
                    dlr_level_text = 'Terminal'
                else:
                    dlr_level_text = 'All'
                dlr_method = updated_request.args['dlr-method'][0]
            else:
                dlr_url = None
                dlr_level = 0
                dlr_level_text = 'No'
                dlr_method = None

            # QoS throttling
            if user.mt_credential.getQuota(
                    'http_throughput') >= 0 and user.getCnxStatus(
                    ).httpapi['qos_last_submit_sm_at'] != 0:
                qos_throughput_second = 1 / float(
                    user.mt_credential.getQuota('http_throughput'))
                qos_throughput_ysecond_td = timedelta(
                    microseconds=qos_throughput_second * 1000000)
                qos_delay = datetime.now() - user.getCnxStatus(
                ).httpapi['qos_last_submit_sm_at']
                if qos_delay < qos_throughput_ysecond_td:
                    self.stats.inc('throughput_error_count')
                    self.log.error(
                        "QoS: submit_sm_event is faster (%s) than fixed throughput (%s), user:%s, rejecting message.",
                        qos_delay, qos_throughput_ysecond_td, user)

                    raise ThroughputExceededError("User throughput exceeded")
            user.getCnxStatus(
            ).httpapi['qos_last_submit_sm_at'] = datetime.now()

            # Get number of PDUs to be sent (for billing purpose)
            _pdu = routable.pdu
            submit_sm_count = 1
            while hasattr(_pdu, 'nextPdu'):
                _pdu = _pdu.nextPdu
                submit_sm_count += 1

            # Pre-sending submit_sm: Billing processing
            bill = route.getBillFor(user)
            self.log.debug(
                "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)",
                bill.bid, bill.getTotalAmounts(), submit_sm_count)
            charging_requirements = []
            u_balance = user.mt_credential.getQuota('balance')
            u_subsm_count = user.mt_credential.getQuota('submit_sm_count')
            if u_balance is not None and bill.getTotalAmounts() > 0:
                # Ensure user have enough balance to pay submit_sm and submit_sm_resp
                charging_requirements.append({
                    'condition':
                    bill.getTotalAmounts() * submit_sm_count <= u_balance,
                    'error_message':
                    'Not enough balance (%s) for charging: %s' %
                    (u_balance, bill.getTotalAmounts())
                })
            if u_subsm_count is not None:
                # Ensure user have enough submit_sm_count to to cover
                # the bill action (decrement_submit_sm_count)
                charging_requirements.append({
                    'condition':
                    bill.getAction('decrement_submit_sm_count') *
                    submit_sm_count <= u_subsm_count,
                    'error_message':
                    'Not enough submit_sm_count (%s) for charging: %s' %
                    (u_subsm_count,
                     bill.getAction('decrement_submit_sm_count'))
                })

            if self.RouterPB.chargeUserForSubmitSms(
                    user, bill, submit_sm_count,
                    charging_requirements) is None:
                self.stats.inc('charging_error_count')
                self.log.error(
                    'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)',
                    user, bill.bid, bill.getTotalAmounts(), submit_sm_count)
                raise ChargingError(
                    'Cannot charge submit_sm, check RouterPB log file for details'
                )

            ########################################################
            # Send SubmitSmPDU through smpp client manager PB server
            self.log.debug(
                "Connector '%s' is set to be a route for this SubmitSmPDU",
                routedConnector.cid)
            c = self.SMPPClientManagerPB.perspective_submit_sm(
                uid=user.uid,
                cid=routedConnector.cid,
                SubmitSmPDU=routable.pdu,
                submit_sm_bill=bill,
                priority=priority,
                pickled=False,
                dlr_url=dlr_url,
                dlr_level=dlr_level,
                dlr_method=dlr_method)

            # Build final response
            if not c.result:
                self.stats.inc('server_error_count')
                self.log.error('Failed to send SubmitSmPDU to [cid:%s]',
                               routedConnector.cid)
                raise ServerError(
                    'Cannot send submit_sm, check SMPPClientManagerPB log file for details'
                )
            else:
                self.stats.inc('success_count')
                self.stats.set('last_success_at', datetime.now())
                self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s',
                               routedConnector.cid, c.result)
                response = {'return': c.result, 'status': 200}
        except Exception as e:
            self.log.error("Error: %s", e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}
        finally:
            self.log.debug("Returning %s to %s.", response,
                           updated_request.getClientIP())
            updated_request.setResponseCode(response['status'])

            # Default return
            _return = 'Error "%s"' % response['return']

            # Success return
            if response['status'] == 200 and routedConnector is not None:
                self.log.info(
                    'SMS-MT [uid:%s] [cid:%s] [msgid:%s] [prio:%s] [dlr:%s] [from:%s] [to:%s] [content:%s]',
                    user.uid, routedConnector.cid, response['return'],
                    priority, dlr_level_text,
                    routable.pdu.params['source_addr'],
                    updated_request.args['to'][0],
                    re.sub(r'[^\x20-\x7E]+', '.', short_message))
                _return = 'Success "%s"' % response['return']

            updated_request.write(_return)
            updated_request.finish()

    def render(self, request):
        """
        /send request processing

        Note: This method MUST behave exactly like jasmin.protocols.smpp.factory.SMPPServerFactory.submit_sm_event
        """

        self.log.debug("Rendering /send response with args: %s from %s",
                       request.args, request.getClientIP())
        request.responseHeaders.addRawHeader(b"content-type", b"text/plain")
        response = {'return': None, 'status': 200}

        self.stats.inc('request_count')
        self.stats.set('last_request_at', datetime.now())

        # updated_request will be filled with default values where request will never get modified
        # updated_request is used for sending the SMS, request is just kept as an original request object
        updated_request = request

        try:
            # Validation (must have almost the same params as /rate service)
            fields = {
                'to': {
                    'optional': False,
                    'pattern': re.compile(r'^\+{0,1}\d+$')
                },
                'from': {
                    'optional': True
                },
                'coding': {
                    'optional': True,
                    'pattern':
                    re.compile(r'^(0|1|2|3|4|5|6|7|8|9|10|13|14){1}$')
                },
                'username': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,15}$')
                },
                'password': {
                    'optional': False,
                    'pattern': re.compile(r'^.{1,8}$')
                },
                # Priority validation pattern can be validated/filtered further more
                # through HttpAPICredentialValidator
                'priority': {
                    'optional': True,
                    'pattern': re.compile(r'^[0-3]$')
                },
                'sdt': {
                    'optional':
                    True,
                    'pattern':
                    re.compile(
                        r'^\d{2}\d{2}\d{2}\d{2}\d{2}\d{2}\d{1}\d{2}(\+|-|R)$')
                },
                # Validity period validation pattern can be validated/filtered further more
                # through HttpAPICredentialValidator
                'validity-period': {
                    'optional': True,
                    'pattern': re.compile(r'^\d+$')
                },
                'dlr': {
                    'optional': False,
                    'pattern': re.compile(r'^(yes|no)$')
                },
                'dlr-url': {
                    'optional': True,
                    'pattern': re.compile(r'^(http|https)\://.*$')
                },
                # DLR Level validation pattern can be validated/filtered further more
                # through HttpAPICredentialValidator
                'dlr-level': {
                    'optional': True,
                    'pattern': re.compile(r'^[1-3]$')
                },
                'dlr-method': {
                    'optional': True,
                    'pattern': re.compile(r'^(get|post)$', re.IGNORECASE)
                },
                'tags': {
                    'optional': True,
                    'pattern': re.compile(r'^([-a-zA-Z0-9,])*$')
                },
                'content': {
                    'optional': True
                },
                'hex-content': {
                    'optional': True
                },
                'custom_tlvs': {
                    'optional': True
                }
            }

            if updated_request.getHeader('content-type') == 'application/json':
                json_body = updated_request.content.read()
                json_data = json.loads(json_body)
                for key, value in json_data.items():
                    # Make the values look like they came from form encoding all surrounded by [ ]
                    if isinstance(value, unicode):
                        value = value.encode()

                    updated_request.args[key.encode()] = [value]

            # If no custom TLVs present, defaujlt to an [] which will be passed down to SubmitSM
            if 'custom_tlvs' not in updated_request.args:
                updated_request.args['custom_tlvs'] = [[]]

            # Default coding is 0 when not provided
            if 'coding' not in updated_request.args:
                updated_request.args['coding'] = ['0']

            # Set default for undefined updated_request.arguments
            if 'dlr-url' in updated_request.args or 'dlr-level' in updated_request.args:
                updated_request.args['dlr'] = ['yes']
            if 'dlr' not in updated_request.args:
                # Setting DLR updated_request to 'no'
                updated_request.args['dlr'] = ['no']

            # Set default values
            if updated_request.args['dlr'][0] == 'yes':
                if 'dlr-level' not in updated_request.args:
                    # If DLR is requested and no dlr-level were provided, assume minimum level (1)
                    updated_request.args['dlr-level'] = [1]
                if 'dlr-method' not in updated_request.args:
                    # If DLR is requested and no dlr-method were provided, assume default (POST)
                    updated_request.args['dlr-method'] = ['POST']

            # DLR method must be uppercase
            if 'dlr-method' in updated_request.args:
                updated_request.args['dlr-method'][0] = updated_request.args[
                    'dlr-method'][0].upper()

            # Make validation
            v = UrlArgsValidator(updated_request, fields)
            v.validate()

            # Check if have content --OR-- hex-content
            # @TODO: make this inside UrlArgsValidator !
            if 'content' not in request.args and 'hex-content' not in request.args:
                raise UrlArgsValidationError(
                    "content or hex-content not present.")
            elif 'content' in request.args and 'hex-content' in request.args:
                raise UrlArgsValidationError(
                    "content and hex-content cannot be used both in same request."
                )

            # Continue routing in a separate thread
            reactor.callFromThread(self.route_routable,
                                   updated_request=updated_request)
        except Exception as e:
            self.log.error("Error: %s", e)

            if hasattr(e, 'code'):
                response = {'return': e.message, 'status': e.code}
            else:
                response = {'return': "Unknown error: %s" % e, 'status': 500}

            self.log.debug("Returning %s to %s.", response,
                           updated_request.getClientIP())
            updated_request.setResponseCode(response['status'])

            return 'Error "%s"' % response['return']
        else:
            return NOT_DONE_YET
Esempio n. 31
0
class SMPPClientSMListener:
    debug_it = {'rejectCount': 0}
    '''
    This is a listener object instanciated for every new SMPP connection, it is responsible of handling 
    SubmitSm, DeliverSm and SubmitSm PDUs for a given SMPP connection
    '''
    def __init__(self, SMPPClientSMListenerConfig, SMPPClientFactory,
                 amqpBroker, redisClient):
        self.config = SMPPClientSMListenerConfig
        self.SMPPClientFactory = SMPPClientFactory
        self.SMPPOperationFactory = SMPPOperationFactory(
            self.SMPPClientFactory.config)
        self.amqpBroker = amqpBroker
        self.redisClient = redisClient
        self.submit_sm_q = None
        self.qos_last_submit_sm_at = None
        self.rejectTimers = {}
        self.qosTimer = None

        # Set pickleProtocol
        self.pickleProtocol = SMPPClientPBConfig(
            self.config.config_file).pickle_protocol

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = logging.FileHandler(filename=self.config.log_file)
            formatter = logging.Formatter(self.config.log_format,
                                          self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

    def setSubmitSmQ(self, queue):
        self.log.debug('Setting a new submit_sm_q: %s' % queue)
        self.submit_sm_q = queue

    def clearRejectTimer(self, msgid):
        if msgid in self.rejectTimers:
            t = self.rejectTimers[msgid]
            if t.active():
                t.cancel()
            del self.rejectTimers[msgid]

    def clearRejectTimers(self):
        for msgid, timer in self.rejectTimers.items():
            if timer.active():
                timer.cancel()
            del self.rejectTimers[msgid]

    def clearQosTimer(self):
        if self.qosTimer is not None and self.qosTimer.called == False:
            self.qosTimer.cancel()
            self.qosTimer = None

    def clearAllTimers(self):
        self.clearQosTimer()
        self.clearRejectTimers()

    @defer.inlineCallbacks
    def rejectAndRequeueMessage(self, message, delay=True):
        msgid = message.content.properties['message-id']

        if delay != False:
            self.log.debug(
                "Requeuing SubmitSmPDU[%s] in %s seconds" %
                (msgid, self.SMPPClientFactory.config.requeue_delay))

            # Use configured requeue_delay or specific one
            if delay is not bool:
                requeue_delay = delay
            else:
                requeue_delay = self.SMPPClientFactory.config.requeue_delay

            # Requeue the message with a delay
            t = reactor.callLater(requeue_delay,
                                  self.rejectMessage,
                                  message=message,
                                  requeue=1)

            # If any, clear timer before setting a new one
            self.clearRejectTimer(msgid)

            self.rejectTimers[msgid] = t
            defer.returnValue(t)
        else:
            self.log.debug("Requeuing SubmitSmPDU[%s] without delay" % msgid)
            yield self.rejectMessage(message, requeue=1)

    @defer.inlineCallbacks
    def rejectMessage(self, message, requeue=0):
        yield self.amqpBroker.chan.basic_reject(
            delivery_tag=message.delivery_tag, requeue=requeue)

    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    @defer.inlineCallbacks
    def setKeyExpiry(self, callbackArg, key, expiry):
        yield self.redisClient.expire(key, expiry)

    @defer.inlineCallbacks
    def submit_sm_callback(self, message):
        """This callback is a queue listener
        it is called whenever a message was consumed from queue
        c.f. test_amqp.ConsumeTestCase for use cases
        """
        msgid = message.content.properties['message-id']
        SubmitSmPDU = pickle.loads(message.content.body)

        self.submit_sm_q.get().addCallback(self.submit_sm_callback).addErrback(
            self.submit_sm_errback)

        self.log.debug(
            "Callbacked a submit_sm with a SubmitSmPDU[%s] (?): %s" %
            (msgid, SubmitSmPDU))

        if self.qos_last_submit_sm_at is None:
            self.qos_last_submit_sm_at = datetime(1970, 1, 1)

        if self.SMPPClientFactory.config.submit_sm_throughput > 0:
            # QoS throttling
            qos_throughput_second = 1 / float(
                self.SMPPClientFactory.config.submit_sm_throughput)
            qos_throughput_ysecond_td = timedelta(
                microseconds=qos_throughput_second * 1000000)
            qos_delay = datetime.now() - self.qos_last_submit_sm_at
            if qos_delay < qos_throughput_ysecond_td:
                qos_slow_down = float((qos_throughput_ysecond_td -
                                       qos_delay).microseconds) / 1000000
                # We're faster than submit_sm_throughput, slow down before taking a new message from the queue
                self.log.debug(
                    "QoS: submit_sm_callback is faster (%s) than fixed throughput (%s), slowing down by %s seconds (message will be requeued)."
                    % (qos_delay, qos_throughput_ysecond_td, qos_slow_down))

                # Relaunch queue callbacking after qos_slow_down seconds
                #self.qosTimer = task.deferLater(reactor, qos_slow_down, self.submit_sm_q.get)
                #self.qosTimer.addCallback(self.submit_sm_callback).addErrback(self.submit_sm_errback)
                # Requeue the message
                yield self.rejectAndRequeueMessage(message,
                                                   delay=qos_slow_down)
                defer.returnValue(False)

            self.qos_last_submit_sm_at = datetime.now()

        # Verify if message is a SubmitSm PDU
        if isinstance(SubmitSmPDU, SubmitSM) == False:
            self.log.error(
                "Received an object[%s] which is not an instance of SubmitSm: discarding this unkown object from the queue"
                % msgid)
            yield self.rejectMessage(message)
            defer.returnValue(False)
        # If the message has expired in the queue
        if 'headers' in message.content.properties and 'expiration' in message.content.properties[
                'headers']:
            expiration_datetime = parser.parse(
                message.content.properties['headers']['expiration'])
            if expiration_datetime < datetime.now():
                self.log.info(
                    "Discarding expired message[%s]: expiration is %s" %
                    (msgid, expiration_datetime))
                yield self.rejectMessage(message)
                defer.returnValue(False)
        # SMPP Client should be already connected
        if self.SMPPClientFactory.smpp == None:
            self.log.error(
                "SMPP Client is not connected: requeuing SubmitSmPDU[%s]" %
                msgid)
            yield self.rejectAndRequeueMessage(message)
            defer.returnValue(False)
        # SMPP Client should be already bound as transceiver or transmitter
        if self.SMPPClientFactory.smpp.isBound() == False:
            self.log.error(
                "SMPP Client is not bound: Requeuing SubmitSmPDU[%s]" % msgid)
            yield self.rejectAndRequeueMessage(message)
            defer.returnValue(False)

        self.log.debug("Sending SubmitSmPDU through SMPPClientFactory")
        yield self.SMPPClientFactory.smpp.sendDataRequest(
            SubmitSmPDU).addCallback(self.submit_sm_resp_event, message)

    @defer.inlineCallbacks
    def submit_sm_resp_event(self, r, amqpMessage):
        msgid = amqpMessage.content.properties['message-id']
        total_bill_amount = None

        if ('headers' not in amqpMessage.content.properties
                or 'submit_sm_resp_bill'
                not in amqpMessage.content.properties['headers']):
            submit_sm_resp_bill = None
        else:
            submit_sm_resp_bill = pickle.loads(
                amqpMessage.content.properties['headers']
                ['submit_sm_resp_bill'])

        if r.response.status == CommandStatus.ESME_ROK:
            # Get bill information
            total_bill_amount = 0.0
            if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts(
            ) > 0:
                total_bill_amount = submit_sm_resp_bill.getTotalAmounts()

            # UDH is set ?
            UDHI_INDICATOR_SET = False
            if hasattr(r.request.params['esm_class'], 'gsmFeatures'):
                for gsmFeature in r.request.params['esm_class'].gsmFeatures:
                    if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                        UDHI_INDICATOR_SET = True
                        break

            # What type of splitting ?
            splitMethod = None
            if 'sar_msg_ref_num' in r.request.params:
                splitMethod = 'sar'
            elif UDHI_INDICATOR_SET and r.request.params[
                    'short_message'][:3] == '\x05\x00\x03':
                splitMethod = 'udh'

            # Concatenate short_message
            if splitMethod is not None:
                _pdu = r.request
                if splitMethod == 'sar':
                    short_message = _pdu.params['short_message']
                else:
                    short_message = _pdu.params['short_message'][6:]

                while hasattr(_pdu, 'nextPdu'):
                    _pdu = _pdu.nextPdu
                    if splitMethod == 'sar':
                        short_message += _pdu.params['short_message']
                    else:
                        short_message += _pdu.params['short_message'][6:]

                    # Increase bill amount for each submit_sm_resp
                    if submit_sm_resp_bill is not None and submit_sm_resp_bill.getTotalAmounts(
                    ) > 0:
                        total_bill_amount += submit_sm_resp_bill.getTotalAmounts(
                        )
            else:
                short_message = r.request.params['short_message']

            self.log.info(
                "SMS-MT [cid:%s] [queue-msgid:%s] [smpp-msgid:%s] [status:%s] [prio:%s] [dlr:%s] [validity:%s] [from:%s] [to:%s] [content:%s]"
                % (self.SMPPClientFactory.config.id, msgid,
                   r.response.params['message_id'], r.response.status,
                   amqpMessage.content.properties['priority'],
                   r.request.params['registered_delivery'].receipt, 'none' if
                   ('headers' not in amqpMessage.content.properties
                    or 'expiration'
                    not in amqpMessage.content.properties['headers']) else
                   amqpMessage.content.properties['headers']['expiration'],
                   r.request.params['source_addr'],
                   r.request.params['destination_addr'], short_message))
        else:
            self.log.info(
                "SMS-MT [cid:%s] [queue-msgid:%s] [status:ERROR/%s] [prio:%s] [dlr:%s] [validity:%s] [from:%s] [to:%s] [content:%s]"
                % (self.SMPPClientFactory.config.id, msgid, r.response.status,
                   amqpMessage.content.properties['priority'],
                   r.request.params['registered_delivery'].receipt, 'none' if
                   ('headers' not in amqpMessage.content.properties
                    or 'expiration'
                    not in amqpMessage.content.properties['headers']) else
                   amqpMessage.content.properties['headers']['expiration'],
                   r.request.params['source_addr'],
                   r.request.params['destination_addr'],
                   r.request.params['short_message']))

        # Cancel any mapped rejectTimer to this message (in case this message was rejected in the past)
        self.clearRejectTimer(msgid)

        self.log.debug("ACKing amqpMessage [%s] having routing_key [%s]",
                       msgid, amqpMessage.routing_key)
        # ACK the message in queue, this will remove it from the queue
        yield self.ackMessage(amqpMessage)

        # Redis client is connected ?
        if self.redisClient is not None:
            # Check for HTTP DLR request from redis 'dlr' key
            # If there's a pending delivery receipt request then serve it
            # back by publishing a DLRContentForHttpapi to the messaging exchange
            pickledDlr = None
            pickledSmppsMap = None
            pickledDlr = yield self.redisClient.get("dlr:%s" % msgid)
            if pickledDlr is None:
                pickledSmppsMap = yield self.redisClient.get("smppsmap:%s" %
                                                             msgid)

            if pickledDlr is not None:
                self.log.debug(
                    'There is a HTTP DLR request for msgid[%s] ...' % (msgid))

                dlr = pickle.loads(pickledDlr)
                dlr_url = dlr['url']
                dlr_level = dlr['level']
                dlr_method = dlr['method']
                dlr_expiry = dlr['expiry']

                if dlr_level in [1, 3]:
                    self.log.debug(
                        'Got DLR information for msgid[%s], url:%s, level:%s' %
                        (msgid, dlr_url, dlr_level))
                    content = DLRContentForHttpapi(
                        str(r.response.status),
                        msgid,
                        dlr_url,
                        # The dlr_url in DLRContentForHttpapi indicates the level
                        # of the actual delivery receipt (1) and not the requested
                        # one (maybe 1 or 3)
                        dlr_level=1,
                        method=dlr_method)
                    routing_key = 'dlr_thrower.http'
                    self.log.debug(
                        "Publishing DLRContentForHttpapi[%s] with routing_key[%s]"
                        % (msgid, routing_key))
                    yield self.amqpBroker.publish(exchange='messaging',
                                                  routing_key=routing_key,
                                                  content=content)

                    # DLR request is removed if:
                    # - If level 1 is requested (SMSC level only)
                    # - SubmitSmResp returned an error (no more delivery will be tracked)
                    #
                    # When level 3 is requested, the DLR will be removed when
                    # receiving a deliver_sm (terminal receipt)
                    if dlr_level == 1 or r.response.status != CommandStatus.ESME_ROK:
                        self.log.debug('Removing DLR request for msgid[%s]' %
                                       msgid)
                        yield self.redisClient.delete("dlr:%s" % msgid)
                else:
                    self.log.debug(
                        'Terminal level receipt is requested, will not send any DLR receipt at this level.'
                    )

                if dlr_level in [2, 3]:
                    # Map received submit_sm_resp's message_id to the msg for later rceipt handling
                    self.log.debug(
                        'Mapping smpp msgid: %s to queue msgid: %s, expiring in %s'
                        % (r.response.params['message_id'], msgid, dlr_expiry))
                    hashKey = "queue-msgid:%s" % r.response.params['message_id']
                    hashValues = {
                        'msgid': msgid,
                        'connector_type': 'httpapi',
                    }
                    self.redisClient.set(
                        hashKey,
                        pickle.dumps(hashValues,
                                     self.pickleProtocol)).addCallback(
                                         self.setKeyExpiry, hashKey,
                                         dlr_expiry)
            elif pickledSmppsMap is not None:
                self.log.debug('There is a SMPPs mapping for msgid[%s] ...' %
                               (msgid))

                smpps_map = pickle.loads(pickledSmppsMap)
                system_id = smpps_map['system_id']
                source_addr = smpps_map['source_addr']
                destination_addr = smpps_map['destination_addr']
                registered_delivery = smpps_map['registered_delivery']
                smpps_map_expiry = smpps_map['expiry']

                # Do we need to forward the receipt to the original sender ?
                if ((r.response.status == CommandStatus.ESME_ROK
                     and str(registered_delivery.receipt) in [
                         'SMSC_DELIVERY_RECEIPT_REQUESTED',
                         'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE'
                     ]) or (r.response.status != CommandStatus.ESME_ROK
                            and str(registered_delivery.receipt)
                            == 'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE')):
                    self.log.debug(
                        'Got DLR information for msgid[%s], registered_deliver%s, system_id:%s'
                        % (msgid, registered_delivery, system_id))

                    content = DLRContentForSmpps(str(r.response.status), msgid,
                                                 system_id, source_addr,
                                                 destination_addr)

                    routing_key = 'dlr_thrower.smpps'
                    self.log.debug(
                        "Publishing DLRContentForSmpps[%s] with routing_key[%s]"
                        % (msgid, routing_key))
                    yield self.amqpBroker.publish(exchange='messaging',
                                                  routing_key=routing_key,
                                                  content=content)

                    # Map received submit_sm_resp's message_id to the msg for later rceipt handling
                    self.log.debug(
                        'Mapping smpp msgid: %s to queue msgid: %s, expiring in %s'
                        % (r.response.params['message_id'], msgid,
                           smpps_map_expiry))
                    hashKey = "queue-msgid:%s" % r.response.params['message_id']
                    hashValues = {
                        'msgid': msgid,
                        'connector_type': 'smpps',
                    }
                    self.redisClient.set(
                        hashKey,
                        pickle.dumps(hashValues,
                                     self.pickleProtocol)).addCallback(
                                         self.setKeyExpiry, hashKey,
                                         smpps_map_expiry)
        else:
            self.log.warn('No valid RC were found while checking msg[%s] !' %
                          msgid)

        # Bill will be charged by bill_request.submit_sm_resp.UID queue consumer
        if total_bill_amount > 0:
            pubQueueName = 'bill_request.submit_sm_resp.%s' % submit_sm_resp_bill.user.uid
            content = SubmitSmRespBillContent(submit_sm_resp_bill.bid,
                                              submit_sm_resp_bill.user.uid,
                                              total_bill_amount)
            self.log.debug(
                "Requesting a SubmitSmRespBillContent from a bill [bid:%s] with routing_key[%s]: %s"
                % (submit_sm_resp_bill.bid, pubQueueName, total_bill_amount))
            yield self.amqpBroker.publish(exchange='billing',
                                          routing_key=pubQueueName,
                                          content=content)

        if self.config.publish_submit_sm_resp:
            # Send back submit_sm_resp to submit.sm.resp.CID queue
            # There's no actual listeners on this queue, it can be used to
            # track submit_sm_resp messages from a 3rd party app
            content = SubmitSmRespContent(r.response,
                                          msgid,
                                          pickleProtocol=self.pickleProtocol)
            self.log.debug(
                "Sending back SubmitSmRespContent[%s] with routing_key[%s]" %
                (msgid, amqpMessage.content.properties['reply-to']))
            yield self.amqpBroker.publish(
                exchange='messaging',
                routing_key=amqpMessage.content.properties['reply-to'],
                content=content)

    def submit_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) == None:
            #@todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside submit_sm_callback
            # - the qosTimer has been cancelled (self.clearQosTimer())
            self.log.error("Error in submit_sm_errback: %s" %
                           error.getErrorMessage())

    @defer.inlineCallbacks
    def concatDeliverSMs(self, HSetReturn, splitMethod, total_segments,
                         msg_ref_num, segment_seqnum):
        hashKey = "longDeliverSm:%s" % (msg_ref_num)
        if HSetReturn != 1:
            self.log.warn('Error (%s) when trying to set hashKey %s' %
                          (HSetReturn, hashKey))
            return

        # @TODO: longDeliverSm part expiry must be configurable
        yield self.redisClient.expire(hashKey, 300)

        # This is the last part
        if segment_seqnum == total_segments:
            hvals = yield self.redisClient.hvals(hashKey)
            if len(hvals) != total_segments:
                self.log.warn(
                    'Received the last part (msg_ref_num:%s) and did not find all parts in redis, data lost !'
                    % msg_ref_num)
                return

            # Get PDUs
            pdus = {}
            for pickledValue in hvals:
                value = pickle.loads(pickledValue)

                pdus[value['segment_seqnum']] = value['pdu']

            # Build short_message
            short_message = ''
            for i in range(total_segments):
                if splitMethod == 'sar':
                    short_message += pdus[i + 1].params['short_message']
                else:
                    short_message += pdus[i + 1].params['short_message'][6:]

            # Build the final pdu and return it back to deliver_sm_event
            pdu = pdus[1]  # Take the first part as a base of work
            # 1. Remove message splitting information from pdu
            if splitMethod == 'sar':
                del (pdu.params['sar_segment_seqnum'])
                del (pdu.params['sar_total_segments'])
                del (pdu.params['sar_msg_ref_num'])
            else:
                pdu.params['esm_class'] = None
            # 2. Set the new short_message
            pdu.params['short_message'] = short_message
            yield self.deliver_sm_event(smpp=None, pdu=pdu, concatenated=True)

    @defer.inlineCallbacks
    def deliver_sm_event(self, smpp, pdu, concatenated=False):
        """This event is called whenever a deliver_sm pdu is received through a SMPPc
        It will hand the pdu to the router or a dlr thrower (depending if its a DLR or not).
        """

        pdu.dlr = self.SMPPOperationFactory.isDeliveryReceipt(pdu)
        content = DeliverSmContent(pdu,
                                   self.SMPPClientFactory.config.id,
                                   pickleProtocol=self.pickleProtocol,
                                   concatenated=concatenated)
        msgid = content.properties['message-id']

        if pdu.dlr is None:
            # We have a SMS-MO

            # UDH is set ?
            UDHI_INDICATOR_SET = False
            if hasattr(pdu.params['esm_class'], 'gsmFeatures'):
                for gsmFeature in pdu.params['esm_class'].gsmFeatures:
                    if str(gsmFeature) == 'UDHI_INDICATOR_SET':
                        UDHI_INDICATOR_SET = True
                        break

            splitMethod = None
            # Is it a part of a long message ?
            if 'sar_msg_ref_num' in pdu.params:
                splitMethod = 'sar'
                total_segments = pdu.params['sar_total_segments']
                segment_seqnum = pdu.params['sar_segment_seqnum']
                msg_ref_num = pdu.params['sar_msg_ref_num']
                self.log.debug(
                    'Received a part of SMS-MO [queue-msgid:%s] using SAR options: total_segments=%s, segmen_seqnum=%s, msg_ref_num=%s'
                    % (msgid, total_segments, segment_seqnum, msg_ref_num))
            elif UDHI_INDICATOR_SET and pdu.params[
                    'short_message'][:3] == '\x05\x00\x03':
                splitMethod = 'udh'
                total_segments = struct.unpack(
                    '!B', pdu.params['short_message'][4])[0]
                segment_seqnum = struct.unpack(
                    '!B', pdu.params['short_message'][5])[0]
                msg_ref_num = struct.unpack('!B',
                                            pdu.params['short_message'][3])[0]
                self.log.debug(
                    'Received a part of SMS-MO [queue-msgid:%s] using UDH options: total_segments=%s, segmen_seqnum=%s, msg_ref_num=%s'
                    % (msgid, total_segments, segment_seqnum, msg_ref_num))

            if splitMethod is None:
                # It's a simple short message or a part of a concatenated message
                routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                self.log.debug(
                    "Publishing DeliverSmContent[%s] with routing_key[%s]" %
                    (msgid, routing_key))
                yield self.amqpBroker.publish(exchange='messaging',
                                              routing_key=routing_key,
                                              content=content)

                self.log.info(
                    "SMS-MO [cid:%s] [queue-msgid:%s] [status:%s] [prio:%s] [validity:%s] [from:%s] [to:%s] [content:%s]"
                    %
                    (self.SMPPClientFactory.config.id, msgid, pdu.status,
                     pdu.params['priority_flag'],
                     pdu.params['validity_period'], pdu.params['source_addr'],
                     pdu.params['destination_addr'],
                     pdu.params['short_message']))
            else:
                # Long message part received
                if self.redisClient is None:
                    self.warn(
                        'No valid RC were found while receiving a part of a long DeliverSm [queue-msgid:%s], MESSAGE IS LOST !'
                        % msgid)

                # Save it to redis
                hashKey = "longDeliverSm:%s" % (msg_ref_num)
                hashValues = {
                    'pdu': pdu,
                    'total_segments': total_segments,
                    'msg_ref_num': msg_ref_num,
                    'segment_seqnum': segment_seqnum
                }
                self.redisClient.hset(
                    hashKey, segment_seqnum,
                    pickle.dumps(hashValues, self.pickleProtocol)).addCallback(
                        self.concatDeliverSMs, splitMethod, total_segments,
                        msg_ref_num, segment_seqnum)

                self.log.info(
                    "DeliverSmContent[%s] is a part of a long message of %s parts, will be sent to queue after concatenation."
                    % (msgid, total_segments))

                # Flag it as "will_be_concatenated" and publish it to router
                routing_key = 'deliver.sm.%s' % self.SMPPClientFactory.config.id
                self.log.debug(
                    "Publishing DeliverSmContent[%s](flagged:wbc) with routing_key[%s]"
                    % (msgid, routing_key))
                content.properties['headers']['will_be_concatenated'] = True
                yield self.amqpBroker.publish(exchange='messaging',
                                              routing_key=routing_key,
                                              content=content)
        else:
            # This is a DLR !
            # Check for DLR request
            if self.redisClient is not None:
                q = yield self.redisClient.get("queue-msgid:%s" %
                                               pdu.dlr['id'])
                submit_sm_queue_id = None
                connector_type = None
                if q is not None:
                    q = pickle.loads(q)
                    submit_sm_queue_id = q['msgid']
                    connector_type = q['connector_type']

                if submit_sm_queue_id is not None and connector_type == 'httpapi':
                    pickledDlr = yield self.redisClient.get("dlr:%s" %
                                                            submit_sm_queue_id)

                    if pickledDlr is not None:
                        dlr = pickle.loads(pickledDlr)
                        dlr_url = dlr['url']
                        dlr_level = dlr['level']
                        dlr_method = dlr['method']

                        if dlr_level in [2, 3]:
                            self.log.debug(
                                'Got DLR information for msgid[%s], url:%s, level:%s'
                                % (submit_sm_queue_id, dlr_url, dlr_level))
                            content = DLRContentForHttpapi(
                                pdu.dlr['stat'],
                                submit_sm_queue_id,
                                dlr_url,
                                # The dlr_url in DLRContentForHttpapi indicates the level
                                # of the actual delivery receipt (2) and not the
                                # requested one (maybe 2 or 3)
                                dlr_level=2,
                                id_smsc=pdu.dlr['id'],
                                sub=pdu.dlr['sub'],
                                dlvrd=pdu.dlr['dlvrd'],
                                subdate=pdu.dlr['sdate'],
                                donedate=pdu.dlr['ddate'],
                                err=pdu.dlr['err'],
                                text=pdu.dlr['text'],
                                method=dlr_method)
                            routing_key = 'dlr_thrower.http'
                            self.log.debug(
                                "Publishing DLRContentForHttpapi[%s] with routing_key[%s]"
                                % (submit_sm_queue_id, routing_key))
                            yield self.amqpBroker.publish(
                                exchange='messaging',
                                routing_key=routing_key,
                                content=content)

                            self.log.debug(
                                'Removing DLR request for msgid[%s]' %
                                submit_sm_queue_id)
                            yield self.redisClient.delete('dlr:%s' %
                                                          submit_sm_queue_id)
                        else:
                            self.log.debug(
                                'SMS-C receipt is requested, will not send any DLR receipt at this level.'
                            )
                    else:
                        self.log.warn(
                            'Got invalid DLR information for msgid[%s], url:%s, level:%s'
                            % (submit_sm_queue_id, dlr_url, dlr_level))
                elif submit_sm_queue_id is not None and connector_type == 'smpps':
                    pickledSmppsMap = yield self.redisClient.get(
                        "smppsmap:%s" % submit_sm_queue_id)

                    if pickledSmppsMap is not None:
                        smpps_map = pickle.loads(pickledSmppsMap)
                        system_id = smpps_map['system_id']
                        source_addr = smpps_map['source_addr']
                        destination_addr = smpps_map['destination_addr']
                        registered_delivery = smpps_map['registered_delivery']
                        smpps_map_expiry = smpps_map['expiry']

                        success_states = ['ACCEPTD', 'DELIVRD']
                        final_states = [
                            'DELIVRD', 'EXPIRED', 'DELETED', 'UNDELIV',
                            'REJECTD'
                        ]
                        # Do we need to forward the receipt to the original sender ?
                        if ((pdu.dlr['stat'] in success_states
                             and str(registered_delivery.receipt) in [
                                 'SMSC_DELIVERY_RECEIPT_REQUESTED',
                                 'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE'
                             ]) or
                            (pdu.dlr['stat'] not in success_states
                             and str(registered_delivery.receipt) ==
                             'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE')):

                            self.log.debug(
                                'Got DLR information for msgid[%s], registered_deliver%s, system_id:%s'
                                % (submit_sm_queue_id, registered_delivery,
                                   system_id))
                            content = DLRContentForSmpps(
                                pdu.dlr['stat'], submit_sm_queue_id, system_id,
                                source_addr, destination_addr)

                            routing_key = 'dlr_thrower.smpps'
                            self.log.debug(
                                "Publishing DLRContentForSmpps[%s] with routing_key[%s]"
                                % (submit_sm_queue_id, routing_key))
                            yield self.amqpBroker.publish(
                                exchange='messaging',
                                routing_key=routing_key,
                                content=content)

                            if pdu.dlr['stat'] in final_states:
                                self.log.debug(
                                    'Removing SMPPs map for msgid[%s]' %
                                    submit_sm_queue_id)
                                yield self.redisClient.delete(
                                    'smppsmap:%s' % submit_sm_queue_id)
                else:
                    self.log.warn('Got a DLR for an unknown message id: %s' %
                                  pdu.dlr['id'])
            else:
                self.log.warn(
                    'DLR for msgid[%s] is not checked, no valid RC were found'
                    % msgid)

            self.log.info(
                "DLR [cid:%s] [smpp-msgid:%s] [status:%s] [submit date:%s] [done date:%s] [submitted/delivered messages:%s/%s] [err:%s] [content:%s]"
                % (
                    self.SMPPClientFactory.config.id,
                    pdu.dlr['id'],
                    pdu.dlr['stat'],
                    pdu.dlr['sdate'],
                    pdu.dlr['ddate'],
                    pdu.dlr['sub'],
                    pdu.dlr['dlvrd'],
                    pdu.dlr['err'],
                    pdu.dlr['text'],
                ))