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