def _serviceHandler(system_id, smpp, pdu):
     self.service_calls.append((system_id, smpp, pdu))
     return DataHandlerResponse(status=pdu_types.CommandStatus.ESME_ROK,
                                message_id='tests',
                                final_date=None,
                                message_state=pdu_types.MessageState.ACCEPTED,
                                error_code=0)
Beispiel #2
0
 def test_data_handler_return_resp(self):
     smpp = self.getProtocolObject()
     smpp.sendPDU = Mock()
     reqPDU = DataSM(6)
     smpp.PDURequestSucceeded(
         DataHandlerResponse(CommandStatus.ESME_RINVSRCTON,
                             delivery_failure_reason=DeliveryFailureReason.
                             PERMANENT_NETWORK_ERROR), reqPDU)
     self.assertEquals(1, smpp.sendPDU.call_count)
     sent = smpp.sendPDU.call_args[0][0]
     self.assertEquals(
         DataSMResp(6,
                    CommandStatus.ESME_RINVSRCTON,
                    delivery_failure_reason=DeliveryFailureReason.
                    PERMANENT_NETWORK_ERROR), sent)
Beispiel #3
0
    def submit_sm_post_interception(self, *args, **kw):
        """This event handler will deliver the submit_sm to the right smppc connector.
        Note that Jasmin deliver submit_sm messages like this:
        - from httpapi to smppc (handled in jasmin.protocols.http.server)
        - from smpps to smppc (this event handler)

        Note: This event handler MUST behave exactly like jasmin.protocols.http.server.Send.render
        """

        try:
            # Init message id & status
            message_id = None
            status = None

            # Post interception:
            if len(args) == 1:
                if isinstance(args[0], bool) and not args[0]:
                    self.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:
                    self.stats.inc('interceptor_error_count')
                    self.log.error(
                        'Interceptor script returned %s smpp_status error.',
                        args[0]['smpp_status'])
                    raise SubmitSmInterceptionError(
                        code=args[0]['smpp_status'])
                elif isinstance(args[0], dict) and args[0]['smpp_status'] == 0:
                    self.stats.inc('interceptor_count')
                    self.log.info(
                        'Interceptor script returned %s success smpp_status.',
                        args[0]['smpp_status'])
                    # Do we have a message_id returned from interceptor ?
                    if 'message_id' in args[0]['extra']:
                        message_id = str(args[0]['extra']['message_id'])
                    raise SubmitSmInterceptionSuccess()
                elif isinstance(args[0], (str, bytes)):
                    self.stats.inc('interceptor_count')
                    routable = pickle.loads(args[0])
                else:
                    self.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])
            else:
                routable = kw['routable']

            system_id = kw['system_id']
            proto = kw['proto']

            self.log.debug(
                'Handling submit_sm_post_interception event for system_id: %s',
                system_id)

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

            # 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.log.error(
                    "Failover route has no bound connector to handle SubmitSmPDU: %s",
                    routable.pdu)
                raise SubmitSmRoutingError()

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

                    raise SubmitSmThroughputExceededError()
            routable.user.getCnxStatus(
            ).smpps['qos_last_submit_sm_at'] = datetime.now()

            # Pre-sending submit_sm: Billing processing
            bill = route.getBillFor(routable.user)
            self.log.debug(
                "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU",
                bill.bid, bill.getTotalAmounts())
            charging_requirements = []
            u_balance = routable.user.mt_credential.getQuota('balance')
            u_subsm_count = routable.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() <= 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') <=
                    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(
                    routable.user, bill,
                    requirements=charging_requirements) is None:
                self.log.error(
                    'Charging user %s failed, [bid:%s] [ttlamounts:%s] (check router log)',
                    routable.user, bill.bid, bill.getTotalAmounts())
                raise SubmitSmChargingError()

            # Get priority value from SubmitSmPDU to pass to SMPPClientManagerPB.perspective_submit_sm()
            priority = 0
            if routable.pdu.params['priority_flag'] is not None:
                priority = routable.pdu.params['priority_flag']._value_

            if self.SMPPClientManagerPB is None:
                self.log.error(
                    '(submit_sm_event/%s) SMPPClientManagerPB not set: submit_sm will not be submitted',
                    system_id)
                return

            ########################################################
            # 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=routable.user.uid,
                cid=routedConnector.cid,
                SubmitSmPDU=routable.pdu,
                submit_sm_bill=bill,
                priority=priority,
                pickled=False,
                source_connector=proto)

            if not hasattr(c, 'result'):
                self.log.error(
                    'Failed to send SubmitSmPDU to [cid:%s], got: %s',
                    routedConnector.cid, c)
                raise SubmitSmRoutingError()

            # Build final response
            if not c.result:
                self.log.error('Failed to send SubmitSmPDU to [cid:%s]',
                               routedConnector.cid)
                raise SubmitSmRoutingError()

            # Otherwise, message_id is defined on ESME_ROK
            message_id = c.result
        except (SubmitSmInterceptionError, SubmitSmInterceptionSuccess,
                InterceptorRunError, SubmitSmRouteNotFoundError,
                SubmitSmThroughputExceededError, SubmitSmChargingError,
                SubmitSmRoutingError) as e:
            # Known exception handling
            status = e.status
        except Exception as e:
            # Unknown exception handling
            self.log.critical('Got an unknown exception: %s', e)
            status = CommandStatus.ESME_RUNKNOWNERR
        else:
            self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s',
                           routedConnector.cid, message_id)

            # Do not log text for privacy reasons
            # Added in #691
            if self.config.log_privacy:
                logged_content = '** %s byte content **' % len(
                    routable.pdu.params['short_message'])
            else:
                if isinstance(routable.pdu.params['short_message'], bytes):
                    logged_content = '%r' % re.sub(
                        rb'[^\x20-\x7E]+', b'.',
                        routable.pdu.params['short_message'])
                else:
                    logged_content = '%r' % re.sub(
                        r'[^\x20-\x7E]+', '.',
                        routable.pdu.params['short_message'])

            self.log.info(
                'SMS-MT [uid:%s] [cid:%s] [msgid:%s] [prio:%s] [from:%s] [to:%s] [content:%s]',
                routable.user.uid, routedConnector.cid, message_id, priority,
                routable.pdu.params['source_addr'],
                routable.pdu.params['destination_addr'], logged_content)
            status = CommandStatus.ESME_ROK
        finally:
            if message_id is not None:
                return DataHandlerResponse(status=status,
                                           message_id=message_id)
            elif status is not None:
                return DataHandlerResponse(status=status)
Beispiel #4
0
    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, bytes)):
                    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 gsmFeature == EsmClassGsmFeatures.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 (dcs.scheme == DataCodingScheme.GSM_MESSAGE_CLASS) and (
                            dcs.schemeData is not None):
                        not_class2 = (dcs.schemeData.msgClass !=
                                      DataCodingGsmMsgClass.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] == b'\x05\x00\x03':
                    splitMethod = 'udh'
                    total_segments = message_content[4]
                    segment_seqnum = message_content[5]
                    msg_ref_num = message_content[3]
                    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))