def test_message_status(self): msgid = 'msgid' system_id = 'username' source_addr = '999' destination_addr = '111' sub_date = datetime.now() source_addr_ton = AddrTon.NATIONAL source_addr_npi = AddrNpi.ISDN dest_addr_ton = AddrTon.NATIONAL dest_addr_npi = AddrNpi.ISDN validStatuses = [ 'ESME_ROK', 'DELIVRD', 'EXPIRED', 'DELETED', 'UNDELIV', 'ACCEPTD', 'UNKNOWN', 'REJECTD', 'ESME_ANYTHING' ] for status in validStatuses: c = DLRContentForSmpps(status, msgid, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi) self.assertEqual(c['headers']['message_status'], status) self.assertRaises(InvalidParameterError, DLRContentForSmpps, 'anystatus', msgid, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi)
def test_normal_without_err(self): message_status = 'DELIVRD' msgid = 'msgid' system_id = 'username' source_addr = '999' destination_addr = '111' sub_date = datetime.now() source_addr_ton = AddrTon.NATIONAL source_addr_npi = AddrNpi.ISDN dest_addr_ton = AddrTon.NATIONAL dest_addr_npi = AddrNpi.ISDN c = DLRContentForSmpps(message_status, msgid, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi) self.assertEqual(c.body, msgid) self.assertEqual(len(c['headers']), 11) self.assertEqual(c['headers']['try-count'], 0) self.assertEqual(c['headers']['message_status'], message_status) self.assertEqual(c['headers']['system_id'], system_id) self.assertEqual(c['headers']['source_addr'], source_addr) self.assertEqual(c['headers']['destination_addr'], destination_addr) self.assertEqual(c['headers']['sub_date'], str(sub_date)) self.assertEqual(c['headers']['source_addr_ton'], str(AddrTon.NATIONAL)) self.assertEqual(c['headers']['source_addr_npi'], str(AddrNpi.ISDN)) self.assertEqual(c['headers']['dest_addr_ton'], str(AddrTon.NATIONAL)) self.assertEqual(c['headers']['dest_addr_npi'], str(AddrNpi.ISDN)) # Default value of err is 99 self.assertEqual(c['headers']['err'], 99)
def test_normal_with_err(self): message_status = 'DELIVRD' msgid = 'msgid' system_id = 'username' source_addr = '999' destination_addr = '111' sub_date = datetime.now() source_addr_ton = AddrTon.NATIONAL source_addr_npi = AddrNpi.ISDN dest_addr_ton = AddrTon.NATIONAL dest_addr_npi = AddrNpi.ISDN err = 56 c = DLRContentForSmpps(message_status, msgid, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi, err=err) self.assertEqual(len(c['headers']), 11) self.assertEqual(c['headers']['err'], err)
def publishDLRContentForSmppapi(self, message_status, msgid, system_id, source_addr, destination_addr, sub_date=None, source_addr_ton='UNKNOWN', source_addr_npi='UNKNOWN', dest_addr_ton='UNKNOWN', dest_addr_npi='UNKNOWN'): if sub_date is None: sub_date = datetime.datetime.now() content = DLRContentForSmpps(message_status, msgid, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi) yield self.amqpBroker.publish(exchange='messaging', routing_key='dlr_thrower.smpps', content=content)
def test_normal(self): message_status = 'DELIVRD' msgid = 'msgid' system_id = 'username' source_addr = '999' destination_addr = '111' c = DLRContentForSmpps(message_status, msgid, system_id, source_addr, destination_addr) self.assertEquals(c.body, msgid) self.assertEquals(len(c['headers']), 5) self.assertEquals(c['headers']['try-count'], 0) self.assertEquals(c['headers']['message_status'], message_status) self.assertEquals(c['headers']['system_id'], system_id) self.assertEquals(c['headers']['source_addr'], source_addr) self.assertEquals(c['headers']['destination_addr'], destination_addr)
def test_message_status(self): msgid = 'msgid' system_id = 'username' source_addr = '999' destination_addr = '111' validStatuses = [ 'ESME_ROK', 'DELIVRD', 'EXPIRED', 'DELETED', 'UNDELIV', 'ACCEPTD', 'UNKNOWN', 'REJECTD', 'ESME_ANYTHING' ] for status in validStatuses: c = DLRContentForSmpps(status, msgid, system_id, source_addr, destination_addr) self.assertEquals(c['headers']['message_status'], status) self.assertRaises(InvalidParameterError, DLRContentForSmpps, 'anystatus', msgid, system_id, source_addr, destination_addr)
def deliver_sm_dlr_callback(self, message): msgid = message.content.properties['message-id'] pdu_cid = message.content.properties['headers']['cid'] pdu_dlr_id = message.content.properties['headers']['dlr_id'] pdu_dlr_ddate = message.content.properties['headers']['dlr_ddate'] pdu_dlr_sdate = message.content.properties['headers']['dlr_sdate'] pdu_dlr_sub = message.content.properties['headers']['dlr_sub'] pdu_dlr_err = message.content.properties['headers']['dlr_err'] pdu_dlr_text = message.content.properties['headers']['dlr_text'] pdu_dlr_dlvrd = message.content.properties['headers']['dlr_dlvrd'] pdu_dlr_status = message.content.body try: if self.redisClient is None: raise RedisError('RC undefined !') if self.redisClient.connected != 1: raise RedisError('RC is offline !') q = yield self.redisClient.hgetall("queue-msgid:%s" % msgid) if len(q) != 2 or 'msgid' not in q or 'connector_type' not in q: raise DLRMapNotFound( 'Got a DLR for an unknown message id: %s (coded:%s)' % (pdu_dlr_id, msgid)) submit_sm_queue_id = q['msgid'] connector_type = q['connector_type'] # Get dlr and ensure it's sc (source_connector) is same as q['connector_type'] dlr = yield self.redisClient.hgetall("dlr:%s" % submit_sm_queue_id) if dlr is None or len(dlr) == 0: raise DLRMapNotFound( 'Got a DLR for an unknown message id: %s (coded:%s)' % (pdu_dlr_id, msgid)) if len(dlr) > 0 and dlr['sc'] != connector_type: raise DLRMapError( 'Found a dlr for msgid:%s with diffrent sc: %s' % (submit_sm_queue_id, dlr['sc'])) if connector_type == 'httpapi': self.log.debug('There is a HTTP DLR request for msgid[%s] ...', msgid) 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) # The dlr_url in DLRContentForHttpapi indicates the level # of the actual delivery receipt (2) and not the # requested one (maybe 2 or 3) self.log.debug( "Publishing DLRContentForHttpapi[%s] with routing_key[%s]", submit_sm_queue_id, 'dlr_thrower.http') yield self.amqpBroker.publish( exchange='messaging', routing_key='dlr_thrower.http', content=DLRContentForHttpapi(pdu_dlr_status, submit_sm_queue_id, dlr_url, dlr_level=2, id_smsc=msgid, 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)) self.log.debug('Removing HTTP dlr map for msgid[%s]', submit_sm_queue_id) yield self.redisClient.delete('dlr:%s' % submit_sm_queue_id) elif connector_type == 'smppsapi': self.log.debug('There is a SMPPs mapping for msgid[%s] ...', msgid) system_id = dlr['system_id'] source_addr_ton = dlr['source_addr_ton'] source_addr_npi = dlr['source_addr_npi'] source_addr = str(dlr['source_addr']) dest_addr_ton = dlr['dest_addr_ton'] dest_addr_npi = dlr['dest_addr_npi'] destination_addr = str(dlr['destination_addr']) sub_date = dlr['sub_date'] registered_delivery_receipt = dlr['rd_receipt'] 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_status in success_states and registered_delivery_receipt == 'SMSC_DELIVERY_RECEIPT_REQUESTED') or (pdu_dlr_status not in success_states and registered_delivery_receipt in [ 'SMSC_DELIVERY_RECEIPT_REQUESTED', '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_receipt, system_id) self.log.debug( "Publishing DLRContentForSmpps[%s] with routing_key[%s]", submit_sm_queue_id, 'dlr_thrower.smpps') yield self.amqpBroker.publish( exchange='messaging', routing_key='dlr_thrower.smpps', content=DLRContentForSmpps( pdu_dlr_status, submit_sm_queue_id, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi)) if pdu_dlr_status in final_states: self.log.debug('Removing SMPPs dlr map for msgid[%s]', submit_sm_queue_id) yield self.redisClient.delete('dlr:%s' % submit_sm_queue_id) except DLRMapError as e: self.log.error('[msgid:%s] DLRMapError: %s', msgid, e) yield self.rejectMessage(message) except RedisError as e: if msgid in self.lookup_retrials and self.lookup_retrials[ msgid] < self.config.dlr_lookup_max_retries: self.log.error('[msgid:%s] (retrials: %s/%s) RedisError: %s', msgid, self.lookup_retrials[msgid], self.config.dlr_lookup_max_retries, e) yield self.rejectAndRequeueMessage(message) else: self.log.error('[msgid:%s] (final) RedisError: %s', msgid, e) yield self.rejectMessage(message) except DLRMapNotFound as e: if msgid in self.lookup_retrials and self.lookup_retrials[ msgid] < self.config.dlr_lookup_max_retries: self.log.error( '[msgid:%s] (retrials: %s/%s) DLRMapNotFound: %s', msgid, self.lookup_retrials[msgid], self.config.dlr_lookup_max_retries, e) yield self.rejectAndRequeueMessage(message) else: self.log.error('[msgid:%s] (final) DLRMapNotFound: %s', msgid, e) yield self.rejectMessage(message) except Exception as e: self.log.error('[msgid:%s] Unknown error (%s): %s', msgid, type(e), e) yield self.rejectMessage(message) else: yield self.ackMessage(message) self.log.info( "DLR [cid:%s] [smpp-msgid:%s] [status:%s] [submit date:%s] [done date:%s] [sub/dlvrd messages:%s/%s] \ [err:%s] [content:%r]", pdu_cid, msgid, pdu_dlr_text, pdu_dlr_sdate, pdu_dlr_ddate, pdu_dlr_sub, pdu_dlr_dlvrd, pdu_dlr_err, pdu_dlr_text)
def submit_sm_resp_dlr_callback(self, message): msgid = message.content.properties['message-id'] dlr_status = message.content.body try: if self.redisClient is None: raise RedisError('RC undefined !') if self.redisClient.connected != 1: raise RedisError('RC is offline !') # Check for 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 dlr = yield self.redisClient.hgetall("dlr:%s" % msgid) if dlr is None or len(dlr) == 0: raise DLRMapNotFound('No dlr map for msgid[%s]' % msgid) if 'sc' not in dlr or dlr['sc'] not in ['httpapi', 'smppsapi']: raise DLRMapError('Fetched unknown dlr: %s' % dlr) if dlr['sc'] == 'httpapi': self.log.debug('There is a HTTP DLR request for msgid[%s] ...', msgid) 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) # The dlr_url in DLRContentForHttpapi indicates the level # of the actual delivery receipt (1) and not the requested # one (maybe 1 or 3) self.log.debug( "Publishing DLRContentForHttpapi[%s] with routing_key[%s]", msgid, 'dlr_thrower.http') yield self.amqpBroker.publish( exchange='messaging', routing_key='dlr_thrower.http', content=DLRContentForHttpapi(dlr_status, msgid, dlr_url, dlr_level=1, method=dlr_method)) # 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 dlr_status != '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] and dlr_status == 'ESME_ROK': smpp_msgid = message.content.properties['headers'][ 'smpp_msgid'] # Map received submit_sm_resp's message_id to the msg for later receipt handling self.log.debug( 'Mapping smpp msgid: %s to queue msgid: %s, expiring in %s', smpp_msgid, msgid, dlr_expiry) hashKey = "queue-msgid:%s" % smpp_msgid hashValues = {'msgid': msgid, 'connector_type': 'httpapi'} yield self.redisClient.hmset(hashKey, hashValues) yield self.redisClient.expire(hashKey, dlr_expiry) elif dlr['sc'] == 'smppsapi': self.log.debug('There is a SMPPs mapping for msgid[%s] ...', msgid) system_id = dlr['system_id'] source_addr_ton = dlr['source_addr_ton'] source_addr_npi = dlr['source_addr_npi'] source_addr = str(dlr['source_addr']) dest_addr_ton = dlr['dest_addr_ton'] dest_addr_npi = dlr['dest_addr_npi'] destination_addr = str(dlr['destination_addr']) sub_date = dlr['sub_date'] registered_delivery_receipt = dlr['rd_receipt'] smpps_map_expiry = dlr['expiry'] # Do we need to forward the receipt to the original sender ? if ((dlr_status == 'ESME_ROK' and registered_delivery_receipt in [ 'SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE', 'SMSC_DELIVERY_RECEIPT_REQUESTED' ]) or (dlr_status != 'ESME_ROK' and 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_receipt, system_id) if (dlr_status != 'ESME_ROK' or (dlr_status == 'ESME_ROK' and self.config.smpp_receipt_on_success_submit_sm_resp)): # Send back a receipt (by throwing deliver_sm or data_sm) self.log.debug( "Publishing DLRContentForSmpps[%s] with routing_key[%s]", msgid, 'dlr_thrower.smpps') yield self.amqpBroker.publish( exchange='messaging', routing_key='dlr_thrower.smpps', content=DLRContentForSmpps( dlr_status, msgid, system_id, source_addr, destination_addr, sub_date, source_addr_ton, source_addr_npi, dest_addr_ton, dest_addr_npi)) if dlr_status == 'ESME_ROK': smpp_msgid = message.content.properties['headers'][ 'smpp_msgid'] # 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', smpp_msgid, msgid, smpps_map_expiry) hashKey = "queue-msgid:%s" % smpp_msgid hashValues = { 'msgid': msgid, 'connector_type': 'smppsapi' } yield self.redisClient.hmset(hashKey, hashValues) yield self.redisClient.expire(hashKey, smpps_map_expiry) except DLRMapError as e: self.log.error('[msgid:%s] DLR Content: %s', msgid, e) yield self.rejectMessage(message) except RedisError as e: if msgid in self.lookup_retrials and self.lookup_retrials[ msgid] < self.config.dlr_lookup_max_retries: self.log.error('[msgid:%s] (retrials: %s/%s) RedisError: %s', msgid, self.lookup_retrials[msgid], self.config.dlr_lookup_max_retries, e) yield self.rejectAndRequeueMessage(message) else: self.log.error('[msgid:%s] (final) RedisError: %s', msgid, e) yield self.rejectMessage(message) except DLRMapNotFound as e: self.log.debug('[msgid:%s] DLRMapNotFound: %s', msgid, e) yield self.rejectMessage(message) except Exception as e: self.log.error('[msgid:%s] Unknown error (%s): %s', msgid, type(e), e) yield self.rejectMessage(message) else: yield self.ackMessage(message)
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 'pdu' 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'] pdu = kw['pdu'] if 'concatenated' in kw: concatenated = kw['concatenated'] else: concatenated = False # Get message_content if 'short_message' in pdu.params: message_content = pdu.params['short_message'] elif 'message_payload' in pdu.params: message_content = pdu.params['message_payload'] 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') temp_routable = pickle.loads(args[0]) pdu = temp_routable.pdu 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) 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 'esm_class' in pdu.params and 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 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 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) # Get values from data_sm or deliver_sm priority_flag = None if 'priority_flag' in pdu.params: priority_flag = pdu.params['priority_flag'] validity_period = None if 'validity_period' in pdu.params: validity_period = pdu.params['validity_period'] 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, priority_flag, validity_period, pdu.params['source_addr'], pdu.params['destination_addr'], re.sub(r'[^\x20-\x7E]+', '.', message_content)) 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} yield 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: _coded_dlr_id = self.code_dlr_msgid(pdu) q = yield self.redisClient.get("queue-msgid:%s" % _coded_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) # The dlr_url in DLRContentForHttpapi indicates the level # of the actual delivery receipt (2) and not the # requested one (maybe 2 or 3) content = DLRContentForHttpapi(pdu.dlr['stat'], submit_sm_queue_id, dlr_url, dlr_level=2, id_smsc=_coded_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('DLR for msgid[%s] not found !', submit_sm_queue_id) 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'] sub_date = smpps_map['sub_date'] registered_delivery = smpps_map['registered_delivery'] 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) == 'SMSC_DELIVERY_RECEIPT_REQUESTED') or (pdu.dlr['stat'] not in success_states and str(registered_delivery.receipt) in ['SMSC_DELIVERY_RECEIPT_REQUESTED', '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, sub_date) 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 (coded:%s)', pdu.dlr['id'], _coded_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] [sub/dlvrd messages:%s/%s] [err:%s] [content:%s]", self.SMPPClientFactory.config.id, _coded_dlr_id, pdu.dlr['stat'], pdu.dlr['sdate'], pdu.dlr['ddate'], pdu.dlr['sub'], pdu.dlr['dlvrd'], pdu.dlr['err'], pdu.dlr['text']) except (InterceptorRunError, DeliverSmInterceptionError) as e: self.log.info("SMS-MO [cid:%s] [istatus:%s] [from:%s] [to:%s] [content:%s]", self.SMPPClientFactory.config.id, e.status, pdu.params['source_addr'], pdu.params['destination_addr'], re.sub(r'[^\x20-\x7E]+', '.', message_content)) # Known exception handling defer.returnValue(DataHandlerResponse(status=e.status)) except Exception, e: # Unknown exception handling self.log.critical('Got an unknown exception: %s', 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 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: # 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'] 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'], re.sub(r'[^\x20-\x7E]+', '.', short_message)) 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 retrys 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 # 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'], re.sub(r'[^\x20-\x7E]+', '.', r.request.params['short_message'])) # 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) # Redis client is connected ? # Check DLR mappings and publish receipt for later throwing 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) # The dlr_url in DLRContentForHttpapi indicates the level # of the actual delivery receipt (1) and not the requested # one (maybe 1 or 3) content = DLRContentForHttpapi(str(r.response.status), msgid, dlr_url, 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] and r.response.status == CommandStatus.ESME_ROK: # Map received submit_sm_resp's message_id to the msg for later receipt 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'].upper().lstrip('0') hashValues = {'msgid': msgid, 'connector_type': 'httpapi',} yield self.redisClient.setex(hashKey, dlr_expiry, pickle.dumps(hashValues, self.pickleProtocol)) 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'] sub_date = smpps_map['sub_date'] 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_FOR_FAILURE', 'SMSC_DELIVERY_RECEIPT_REQUESTED']) 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) if (r.response.status != CommandStatus.ESME_ROK or (r.response.status == CommandStatus.ESME_ROK and self.config.smpp_receipt_on_success_submit_sm_resp)): # Send back a receipt (by throwing deliver_sm or data_sm) content = DLRContentForSmpps(str(r.response.status), msgid, system_id, source_addr, destination_addr, sub_date) 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) if r.response.status == CommandStatus.ESME_ROK: # 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'].upper().lstrip('0') hashValues = {'msgid': msgid, 'connector_type': 'smpps',} yield self.redisClient.setex(hashKey, smpps_map_expiry, pickle.dumps(hashValues, self.pickleProtocol)) 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) if will_be_retried: defer.returnValue(False)
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). Note: this event will catch data_sm pdus as well """ 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'], re.sub(r'[^\x20-\x7E]+', '.', 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: _coded_dlr_id = self.code_dlr_msgid(pdu) q = yield self.redisClient.get("queue-msgid:%s" % _coded_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=_coded_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('DLR for msgid[%s] not found !' % (submit_sm_queue_id)) 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'] sub_date = smpps_map['sub_date'] 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) == 'SMSC_DELIVERY_RECEIPT_REQUESTED') or (pdu.dlr['stat'] not in success_states and str(registered_delivery.receipt) in [ 'SMSC_DELIVERY_RECEIPT_REQUESTED', '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, sub_date) 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 (coded:%s)' % (pdu.dlr['id'], _coded_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] [sub/dlvrd messages:%s/%s] [err:%s] [content:%s]" % ( self.SMPPClientFactory.config.id, _coded_dlr_id, pdu.dlr['stat'], pdu.dlr['sdate'], pdu.dlr['ddate'], pdu.dlr['sub'], pdu.dlr['dlvrd'], pdu.dlr['err'], pdu.dlr['text'], ))
def publishDLRContentForSmppapi(self, message_status, msgid, system_id, source_addr, destination_addr): content = DLRContentForSmpps(message_status, msgid, system_id, source_addr, destination_addr) yield self.amqpBroker.publish(exchange='messaging', routing_key='dlr_thrower.smpps', content=content)
http_status) # Returning a msgid with ESME_ROK if routable.user.uid == 'entel': extra['message_id'] = claim_msgid_for_uid(routable.user.uid) else: extra['message_id'] = str(uuid.uuid4()) # Send back a DLR status = 'DELIVRD' dlr_content = DLRContentForSmpps( status, extra['message_id'], routable.user.uid, submit_sm.params['source_addr'], submit_sm.params['destination_addr'], datetime.now(), str(submit_sm.params['dest_addr_ton']), str(submit_sm.params['dest_addr_npi']), str(submit_sm.params['source_addr_ton']), str(submit_sm.params['source_addr_npi']), ) # Publish DLR routing_key = 'dlr_thrower.smpps' channel.basic_publish( 'messaging', routing_key, dlr_content.body, pika.BasicProperties(message_id=dlr_content.properties['message-id'], headers=dlr_content.properties['headers'])) logger.info("Published DLRContentForSmpps[%s] to [%s], having status = %s", extra['message_id'], routing_key, status)