def test_submitsmresp(self): # Successful submit_sm_resp c_success = DLR(pdu_type=CommandId.submit_sm_resp, msgid=1, status=CommandStatus.ESME_ROK, smpp_msgid=b'2') # Errored submit_sm_resp c_errored = DLR(pdu_type=CommandId.submit_sm_resp, msgid=3, status=CommandStatus.ESME_RINVPARLEN) self.assertEqual('ESME_ROK', c_success.body) self.assertEqual('1', c_success.properties['message-id']) self.assertEqual(CommandId.submit_sm_resp.name, c_success.properties['headers']['type']) self.assertEqual('2', c_success.properties['headers']['smpp_msgid']) self.assertEqual('ESME_RINVPARLEN', c_errored.body) self.assertEqual('3', c_errored.properties['message-id']) self.assertEqual(CommandId.submit_sm_resp.name, c_errored.properties['headers']['type']) # Exceptions: self.assertRaises(InvalidParameterError, DLR, pdu_type=CommandId.submit_sm_resp, msgid=1, status=CommandStatus.ESME_ROK)
def test_deliversm_and_datasm(self): for pdu_type in [CommandId.deliver_sm, CommandId.data_sm]: c = DLR(pdu_type=pdu_type, msgid=1, status=CommandStatus.ESME_ROK, cid='test', dlr_details={'some': 'detail'}) self.assertEqual(CommandStatus.ESME_ROK.name, c.body) self.assertEqual('1', c.properties['message-id']) self.assertEqual(pdu_type.name, c.properties['headers']['type']) self.assertEqual('test', c.properties['headers']['cid']) self.assertEqual('detail', c.properties['headers']['dlr_some']) # Exceptions: self.assertRaises(InvalidParameterError, DLR, pdu_type=pdu_type, msgid=1, status=CommandStatus.ESME_ROK) self.assertRaises(InvalidParameterError, DLR, pdu_type=pdu_type, msgid=1, status=CommandStatus.ESME_ROK, dlr_details={'some': 'detail'}) self.assertRaises(InvalidParameterError, DLR, pdu_type=pdu_type, msgid=1, status=CommandStatus.ESME_ROK, cid='test')
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))
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)