def test_equality_with_array_and_set(self): r1 = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED, {RegisteredDeliverySmeOriginatedAcks.SME_DELIVERY_ACK_REQUESTED}) r2 = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED, [RegisteredDeliverySmeOriginatedAcks.SME_DELIVERY_ACK_REQUESTED]) self.assertEqual(r1, r2)
def test_equality_with_array_duplicates(self): r1 = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED, [ RegisteredDeliverySmeOriginatedAcks.SME_MANUAL_ACK_REQUESTED, RegisteredDeliverySmeOriginatedAcks.SME_MANUAL_ACK_REQUESTED ]) r2 = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED, [RegisteredDeliverySmeOriginatedAcks.SME_MANUAL_ACK_REQUESTED]) self.assertEqual(r1, r2)
def test_conversion(self): value = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED, [ RegisteredDeliverySmeOriginatedAcks.SME_DELIVERY_ACK_REQUESTED, RegisteredDeliverySmeOriginatedAcks.SME_MANUAL_ACK_REQUESTED ], True) self.do_conversion_test(RegisteredDeliveryEncoder(), value, b'1d') self.do_null_encode_test( RegisteredDeliveryEncoder(), RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED, [], False), b'00')
def test_DeliverSM_with_subaddress(self): pdu = DeliverSM( 1, service_type='BM8', source_addr_ton=AddrTon.INTERNATIONAL, source_addr_npi=AddrNpi.ISDN, source_addr='46123456789', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.ISDN, destination_addr='14046653410', esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.DEFAULT), protocol_id=0, priority_flag=PriorityFlag.LEVEL_0, registered_delivery=RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED), replace_if_present_flag=ReplaceIfPresentFlag.DO_NOT_REPLACE, data_coding=DataCoding( DataCodingScheme.GSM_MESSAGE_CLASS, DataCodingGsmMsg(DataCodingGsmMsgCoding.DEFAULT_ALPHABET, DataCodingGsmMsgClass.CLASS_2)), short_message=b"Hello I'm a bigg fan of you", source_subaddress=Subaddress(SubaddressTypeTag.USER_SPECIFIED, b'742'), dest_subaddress=Subaddress(SubaddressTypeTag.USER_SPECIFIED, b'4131'), ) self.do_conversion_test( PDUEncoder(), pdu, b'00000066000000050000000000000001424d38000101343631323334353637383900010131343034363635333431300000000000000000f2001b48656c6c6f2049276d206120626967672066616e206f6620796f7502020004a037343202030005a034313331' )
def test_DeliverSM_sybase_MO_conversion(self): pdu = DeliverSM( 1, service_type='CMT', source_addr_ton=AddrTon.INTERNATIONAL, source_addr_npi=AddrNpi.UNKNOWN, source_addr='3411149500001', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.UNKNOWN, destination_addr='12345455', esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.DEFAULT), protocol_id=0, priority_flag=PriorityFlag.LEVEL_0, registered_delivery=RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED), replace_if_present_flag=ReplaceIfPresentFlag.DO_NOT_REPLACE, data_coding=DataCoding( DataCodingScheme.GSM_MESSAGE_CLASS, DataCodingGsmMsg(DataCodingGsmMsgCoding.DEFAULT_ALPHABET, DataCodingGsmMsgClass.CLASS_2)), short_message=b'HELLO\x00', ) self.do_conversion_test( PDUEncoder(), pdu, b'0000003f000000050000000000000001434d540001003334313131343935303030303100010031323334353435350000000000000000f2000648454c4c4f00' )
def test_DeliverSM_handset_ack_conversion(self): pdu = DeliverSM( 10, service_type='CMT', source_addr_ton=AddrTon.INTERNATIONAL, source_addr_npi=AddrNpi.UNKNOWN, source_addr='6515555678', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.UNKNOWN, destination_addr='123', esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.SMSC_DELIVERY_RECEIPT), protocol_id=0, priority_flag=PriorityFlag.LEVEL_0, registered_delivery=RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED), replace_if_present_flag=ReplaceIfPresentFlag.DO_NOT_REPLACE, data_coding=DataCoding( scheme_data=DataCodingDefault.SMSC_DEFAULT_ALPHABET), short_message= b'id:1891273321 sub:001 dlvrd:001 submit date:1305050826 done date:1305050826 stat:DELIVRD err:000 Text:DLVRD TO MOBILE\x00', message_state=MessageState.DELIVERED, receipted_message_id='70BA8A69', ) self.do_conversion_test( PDUEncoder(), pdu, b'000000b900000005000000000000000a434d5400010036353135353535363738000100313233000400000000000000007669643a31383931323733333231207375623a30303120646c7672643a303031207375626d697420646174653a3133303530353038323620646f6e6520646174653a3133303530353038323620737461743a44454c49565244206572723a30303020546578743a444c56524420544f204d4f42494c45000427000102001e0009373042413841363900' )
def test_SubmitSM_conversion(self): pdu = SubmitSM( 9284, service_type='', source_addr_ton=AddrTon.ALPHANUMERIC, source_addr_npi=AddrNpi.UNKNOWN, source_addr='mobileway', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.ISDN, destination_addr='1208230', esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.DEFAULT), protocol_id=0, priority_flag=PriorityFlag.LEVEL_0, registered_delivery=RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED), replace_if_present_flag=ReplaceIfPresentFlag.DO_NOT_REPLACE, data_coding=DataCoding( DataCodingScheme.GSM_MESSAGE_CLASS, DataCodingGsmMsg(DataCodingGsmMsgCoding.DEFAULT_ALPHABET, DataCodingGsmMsgClass.CLASS_2)), short_message=b'HELLO', ) self.do_conversion_test( PDUEncoder(), pdu, b'000000360000000400000000000024440005006d6f62696c65776179000101313230383233300000000000000100f2000548454c4c4f' )
def _checkSendAuthorizations(self): """MT Authorizations check""" if not self.user.mt_credential.getAuthorization('smpps_send'): raise AuthorizationError( 'Authorization failed for username [%s] (Can not send MT messages).' % self.user) if (not self.user.mt_credential.getAuthorization('set_dlr_level') and self.submit_sm.params['registered_delivery'] != RegisteredDelivery(RegisteredDeliveryReceipt. NO_SMSC_DELIVERY_RECEIPT_REQUESTED)): raise AuthorizationError( 'Authorization failed for username [%s] (Setting dlr level is not authorized).' % self.user) if (not self.user.mt_credential.getAuthorization('set_source_address') and len(self.submit_sm.params['source_addr']) > 0): raise AuthorizationError( 'Authorization failed for username [%s] (Setting source address is not authorized).' % self.user) if (not self.user.mt_credential.getAuthorization('set_priority') and self._convert_to_string('priority_flag') != priority_flag_value_map[0]): raise AuthorizationError( 'Authorization failed for username [%s] (Setting priority is not authorized).' % self.user)
def test_authorized_set_dlr_level(self): user = self.routerpb_factory.getUser('u1') user.mt_credential.setAuthorization('set_dlr_level', True) # Connect and bind yield self.smppc_factory.connectAndBind() self.assertEqual(self.smppc_factory.smpp.sessionState, SMPPSessionStates.BOUND_TRX) # Install mockers self.smppc_factory.lastProto.PDUReceived = Mock( wraps=self.smppc_factory.lastProto.PDUReceived) # SMPPClient > SMPPServer SubmitSmPDU = copy.deepcopy(self.SubmitSmPDU) SubmitSmPDU.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt. SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE) yield self.smppc_factory.lastProto.sendDataRequest(SubmitSmPDU) # Unbind & Disconnect yield self.smppc_factory.smpp.unbindAndDisconnect() self.assertEqual(self.smppc_factory.smpp.sessionState, SMPPSessionStates.UNBOUND) # Asserts SMPPClient side self.assertEqual(self.smppc_factory.lastProto.PDUReceived.call_count, 2) self.assertEqual( self.smppc_factory.lastProto.PDUReceived.call_args_list[0][0] [0].id, CommandId.submit_sm_resp) self.assertEqual( self.smppc_factory.lastProto.PDUReceived.call_args_list[0][0] [0].status, CommandStatus.ESME_ROK)
def test_SubmitSM_ringtone_conversion(self): pdu = SubmitSM( 455569, service_type='', source_addr_ton=AddrTon.ALPHANUMERIC, source_addr_npi=AddrNpi.UNKNOWN, source_addr='mobileway', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.ISDN, destination_addr='3369809342', esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.DEFAULT, [EsmClassGsmFeatures.UDHI_INDICATOR_SET]), protocol_id=0, priority_flag=PriorityFlag.LEVEL_0, registered_delivery=RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED), replace_if_present_flag=ReplaceIfPresentFlag.DO_NOT_REPLACE, data_coding=DataCoding( DataCodingScheme.GSM_MESSAGE_CLASS, DataCodingGsmMsg(DataCodingGsmMsgCoding.DATA_8BIT, DataCodingGsmMsgClass.CLASS_1)), short_message=binascii.a2b_hex( b'06050415811581024a3a5db5a5cdcda5bdb8040084d8c51381481381481381481381481381381481581681781881881061881061b81081181081881061881061681081781081881061881061b81081181081881061881061681081781081b81881321081b81881221081b818811210824dc1446000' )) self.do_conversion_test( PDUEncoder(), pdu, b'000000a900000004000000000006f3910005006d6f62696c65776179000101333336393830393334320040000000000100f5007506050415811581024a3a5db5a5cdcda5bdb8040084d8c51381481381481381481381481381381481581681781881881061881061b81081181081881061881061681081781081881061881061b81081181081881061881061681081781081b81881321081b81881221081b818811210824dc1446000' )
def test_receive_NACK_deliver_sm_on_delivery_error(self): yield self.connect('127.0.0.1', self.pbPort) yield self.prepareRoutingsAndStartConnector( port=self.ErrorOnSubmitSMSCPort.getHost().port) # Bind yield self.smppc_factory.connectAndBind() # Install mocks self.smpps_factory.lastProto.sendPDU = Mock( wraps=self.smpps_factory.lastProto.sendPDU) # Send a SMS MT through smpps interface SubmitSmPDU = copy.deepcopy(self.SubmitSmPDU) SubmitSmPDU.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt. SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE) yield self.smppc_factory.lastProto.sendDataRequest(SubmitSmPDU) # Wait 1 seconds for submit_sm_resp yield waitFor(1) # Unbind & Disconnect yield self.smppc_factory.smpp.unbindAndDisconnect() yield self.stopSmppClientConnectors() # Run tests self.assertEqual(self.smpps_factory.lastProto.sendPDU.call_count, 3) # smpps response was (1) a submit_sm_resp with ESME_ROK response_pdu_1 = self.smpps_factory.lastProto.sendPDU.call_args_list[ 0][0][0] self.assertEqual(response_pdu_1.id, pdu_types.CommandId.submit_sm_resp) self.assertEqual(response_pdu_1.seqNum, 2) self.assertEqual(response_pdu_1.status, pdu_types.CommandStatus.ESME_ROK) self.assertTrue(response_pdu_1.params['message_id'] is not None) # (2) a deliver_sm response_pdu_2 = self.smpps_factory.lastProto.sendPDU.call_args_list[ 1][0][0] self.assertEqual(response_pdu_2.id, pdu_types.CommandId.deliver_sm) self.assertEqual(response_pdu_2.seqNum, 1) self.assertEqual(response_pdu_2.status, pdu_types.CommandStatus.ESME_ROK) self.assertEqual(response_pdu_2.params['source_addr'], SubmitSmPDU.params['destination_addr']) self.assertEqual(response_pdu_2.params['destination_addr'], SubmitSmPDU.params['source_addr']) self.assertEqual(response_pdu_2.params['receipted_message_id'], response_pdu_1.params['message_id']) self.assertEqual(response_pdu_2.params['message_state'], MessageState.UNDELIVERABLE)
def test_DeliverSM_syniverse_MO_conversion(self): pdu = DeliverSM( 2676551972, service_type='AWSBD', source_addr_ton=AddrTon.INTERNATIONAL, source_addr_npi=AddrNpi.ISDN, source_addr='16505551234', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.ISDN, destination_addr='17735554070', esm_class=EsmClass(EsmClassMode.DEFAULT, EsmClassType.DEFAULT), protocol_id=0, priority_flag=PriorityFlag.LEVEL_0, registered_delivery=RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED), replace_if_present_flag=ReplaceIfPresentFlag.DO_NOT_REPLACE, data_coding=DataCoding(scheme_data=DataCodingDefault.LATIN_1), short_message=b'there is no spoon', ) self.do_conversion_test( PDUEncoder(), pdu, b'0000004d00000005000000009f88f12441575342440001013136353035353531323334000101313737333535353430373000000000000000000300117468657265206973206e6f2073706f6f6e' )
def test_no_charging_on_delivery_error(self): """This test case we: - User have early_decrement_balance_percent set to 10 (%) - Submit a message to SMPPs - Message is routed to a SMPPc - Delivery is errored (submit_sm_resp received in SMPPc and transmitted as a deliver_sm through SMPPs) The user will be charged for 10% of the rate, the rest is not charged because message were not delivered to destination. """ yield self.connect('127.0.0.1', self.pbPort) mt_c = MtMessagingCredential() mt_c.setQuota('balance', 2.0) mt_c.setQuota('submit_sm_count', 10) mt_c.setQuota('early_decrement_balance_percent', 10) user = User(1, Group(1), 'username', 'password', mt_c) yield self.prepareRoutingsAndStartConnector( route_rate=1.0, user=user, port=self.ErrorOnSubmitSMSCPort.getHost().port) # Bind yield self.smppc_factory.connectAndBind() # Install mocks self.smpps_factory.lastProto.sendPDU = Mock( wraps=self.smpps_factory.lastProto.sendPDU) # Send a SMS MT through smpps interface SubmitSmPDU = copy.deepcopy(self.SubmitSmPDU) SubmitSmPDU.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt. SMSC_DELIVERY_RECEIPT_REQUESTED_FOR_FAILURE) yield self.smppc_factory.lastProto.sendDataRequest(SubmitSmPDU) # Wait 1 seconds for submit_sm_resp yield waitFor(1) # Unbind & Disconnect yield self.smppc_factory.smpp.unbindAndDisconnect() yield self.stopSmppClientConnectors() # Run tests self.assertEqual(self.smpps_factory.lastProto.sendPDU.call_count, 3) # smpps response was (1) a submit_sm_resp with ESME_ROK and response_pdu_1 = self.smpps_factory.lastProto.sendPDU.call_args_list[ 0][0][0] self.assertEqual(response_pdu_1.id, pdu_types.CommandId.submit_sm_resp) self.assertEqual(response_pdu_1.seqNum, 2) self.assertEqual(response_pdu_1.status, pdu_types.CommandStatus.ESME_ROK) self.assertTrue(response_pdu_1.params['message_id'] is not None) # (2) a deliver_sm response_pdu_2 = self.smpps_factory.lastProto.sendPDU.call_args_list[ 1][0][0] self.assertEqual(response_pdu_2.id, pdu_types.CommandId.deliver_sm) self.assertEqual(response_pdu_2.params['message_state'], MessageState.UNDELIVERABLE) # (3) an unbind_resp response_pdu_3 = self.smpps_factory.lastProto.sendPDU.call_args_list[ 2][0][0] self.assertEqual(response_pdu_3.id, pdu_types.CommandId.unbind_resp) # Assert quotas after SMS is sent (only 10% were charged) assertionUser = self.pbRoot_f.getUser(user.uid) self.assertAlmostEqual(assertionUser.mt_credential.getQuota('balance'), 1.9) self.assertAlmostEqual( assertionUser.mt_credential.getQuota('submit_sm_count'), 9)
def __init__(self, **kwargs): ##################### # Generic configuration block # cid validation if kwargs.get('id', None) == None: raise ConfigUndefinedIdError('SMPPConfig must have an id') idcheck = re.compile(r'^[A-Za-z0-9_-]{3,25}$') if idcheck.match(str(kwargs.get('id'))) == None: raise ConfigInvalidIdError('SMPPConfig id syntax is invalid') self.id = str(kwargs.get('id')) self.port = kwargs.get('port', 2775) if not isinstance(self.port, int): raise TypeMismatch('port must be an integer') # Logging configuration self.log_file = kwargs.get('log_file', '%s/default-%s.log' % (LOG_PATH, self.id)) self.log_rotate = kwargs.get('log_rotate', 'midnight') self.log_level = kwargs.get('log_level', logging.INFO) self.log_format = kwargs.get( 'log_format', '%(asctime)s %(levelname)-8s %(process)d %(message)s') self.log_date_format = kwargs.get('log_dateformat', '%Y-%m-%d %H:%M:%S') self.log_privacy = kwargs.get('log_privacy', False) if not isinstance(self.log_privacy, bool): raise TypeMismatch('log_privacy must be a boolean') # Timeout for response to bind request self.sessionInitTimerSecs = kwargs.get('sessionInitTimerSecs', 30) if (not isinstance(self.sessionInitTimerSecs, int) and not isinstance(self.sessionInitTimerSecs, float)): raise TypeMismatch( 'sessionInitTimerSecs must be an integer or float') # Enquire link interval self.enquireLinkTimerSecs = kwargs.get('enquireLinkTimerSecs', 30) if (not isinstance(self.enquireLinkTimerSecs, int) and not isinstance(self.enquireLinkTimerSecs, float)): raise TypeMismatch( 'enquireLinkTimerSecs must be an integer or float') # Maximum time lapse allowed between transactions, after which, # the connection is considered as inactive and will reconnect self.inactivityTimerSecs = kwargs.get('inactivityTimerSecs', 300) if not isinstance(self.inactivityTimerSecs, int) and not isinstance( self.inactivityTimerSecs, float): raise TypeMismatch( 'inactivityTimerSecs must be an integer or float') # Timeout for responses to any request PDU self.responseTimerSecs = kwargs.get('responseTimerSecs', 120) if not isinstance(self.responseTimerSecs, int) and not isinstance( self.responseTimerSecs, float): raise TypeMismatch('responseTimerSecs must be an integer or float') # Timeout for reading a single PDU, this is the maximum lapse of time between # receiving PDU's header and its complete read, if the PDU reading timed out, # the connection is considered as 'corrupt' and will reconnect self.pduReadTimerSecs = kwargs.get('pduReadTimerSecs', 10) if not isinstance(self.pduReadTimerSecs, int) and not isinstance( self.pduReadTimerSecs, float): raise TypeMismatch('pduReadTimerSecs must be an integer or float') # DLR # How much time a message is kept in redis waiting for receipt self.dlr_expiry = kwargs.get('dlr_expiry', 86400) if not isinstance(self.dlr_expiry, int) and not isinstance( self.dlr_expiry, float): raise TypeMismatch('dlr_expiry must be an integer or float') ##################### # SMPPClient Specific configuration block self.host = kwargs.get('host', '127.0.0.1') if not isinstance(self.host, str): raise TypeMismatch('host must be a string') self.username = kwargs.get('username', 'smppclient') if len(self.username) > 15: raise TypeMismatch('username is longer than allowed size (15)') self.password = kwargs.get('password', 'password') if len(self.password) > 16: raise TypeMismatch('password is longer than allowed size (16)') self.systemType = kwargs.get('systemType', '') # Reconnection self.reconnectOnConnectionLoss = kwargs.get( 'reconnectOnConnectionLoss', True) if not isinstance(self.reconnectOnConnectionLoss, bool): raise TypeMismatch('reconnectOnConnectionLoss must be a boolean') self.reconnectOnConnectionFailure = kwargs.get( 'reconnectOnConnectionFailure', True) if not isinstance(self.reconnectOnConnectionFailure, bool): raise TypeMismatch( 'reconnectOnConnectionFailure must be a boolean') self.reconnectOnConnectionLossDelay = kwargs.get( 'reconnectOnConnectionLossDelay', 10) if (not isinstance(self.reconnectOnConnectionLossDelay, int) and not isinstance(self.reconnectOnConnectionLossDelay, float)): raise TypeMismatch( 'reconnectOnConnectionLossDelay must be an integer or float') self.reconnectOnConnectionFailureDelay = kwargs.get( 'reconnectOnConnectionFailureDelay', 10) if (not isinstance(self.reconnectOnConnectionFailureDelay, int) and not isinstance(self.reconnectOnConnectionFailureDelay, float)): raise TypeMismatch( 'reconnectOnConnectionFailureDelay must be an integer or float' ) self.useSSL = kwargs.get('useSSL', False) self.SSLCertificateFile = kwargs.get('SSLCertificateFile', None) # Type of bind operation, can be one of these: # - transceiver # - transmitter # - receiver self.bindOperation = kwargs.get('bindOperation', 'transceiver') if self.bindOperation not in [ 'transceiver', 'transmitter', 'receiver' ]: raise UnknownValue('Invalid bindOperation: %s' % self.bindOperation) # These are default parameters, c.f. _setConfigParamsInPDU method in SMPPOperationFactory self.service_type = kwargs.get('service_type', None) self.addressTon = kwargs.get('addressTon', AddrTon.UNKNOWN) self.addressNpi = kwargs.get('addressNpi', AddrNpi.UNKNOWN) self.source_addr_ton = kwargs.get('source_addr_ton', AddrTon.NATIONAL) self.source_addr_npi = kwargs.get('source_addr_npi', AddrNpi.ISDN) self.dest_addr_ton = kwargs.get('dest_addr_ton', AddrTon.INTERNATIONAL) self.dest_addr_npi = kwargs.get('dest_addr_npi', AddrNpi.ISDN) self.addressRange = kwargs.get('addressRange', None) self.source_addr = kwargs.get('source_addr', None) self.esm_class = kwargs.get( 'esm_class', EsmClass(EsmClassMode.STORE_AND_FORWARD, EsmClassType.DEFAULT)) self.protocol_id = kwargs.get('protocol_id', None) self.priority_flag = kwargs.get('priority_flag', PriorityFlag.LEVEL_0) self.schedule_delivery_time = kwargs.get('schedule_delivery_time', None) self.validity_period = kwargs.get('validity_period', None) self.registered_delivery = kwargs.get( 'registered_delivery', RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED)) self.replace_if_present_flag = kwargs.get( 'replace_if_present_flag', ReplaceIfPresentFlag.DO_NOT_REPLACE) self.sm_default_msg_id = kwargs.get('sm_default_msg_id', 0) # 5.2.19 data_coding / c. There is no default setting for the data_coding parameter. # Possible values: # SMSC_DEFAULT_ALPHABET: 0x00 / 0 # IA5_ASCII: 0x01 / 1 # OCTET_UNSPECIFIED: 0x02 / 2 # LATIN_1: 0x03 / 3 # OCTET_UNSPECIFIED_COMMON: 0x04 / 4 # JIS: 0x05 / 5 # CYRILLIC: 0x06 / 6 # ISO_8859_8: 0x07 / 7 # UCS2: 0x08 / 8 # PICTOGRAM: 0x09 / 9 # ISO_2022_JP: 0x0a / 10 # EXTENDED_KANJI_JIS: 0x0d / 13 # KS_C_5601: 0x0e / 14 self.data_coding = kwargs.get('data_coding', 0) if self.data_coding not in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13, 14]: raise UnknownValue('Invalid data_coding: %s' % self.data_coding) # QoS # Rejected messages are requeued with a fixed delay self.requeue_delay = kwargs.get('requeue_delay', 120) if not isinstance(self.requeue_delay, int) and not isinstance( self.requeue_delay, float): raise TypeMismatch('requeue_delay must be an integer or float') self.submit_sm_throughput = kwargs.get('submit_sm_throughput', 1) if (not isinstance(self.submit_sm_throughput, int) and not isinstance(self.submit_sm_throughput, float)): raise TypeMismatch( 'submit_sm_throughput must be an integer or float') # DLR Message id bases from submit_sm_resp to deliver_sm, possible values: # [0] (default) : submit_sm_resp and deliver_sm messages IDs are on the same base. # [1] : submit_sm_resp msg-id is in hexadecimal base, deliver_sm msg-id is in # decimal base. # [2] : submit_sm_resp msg-id is in decimal base, deliver_sm msg-id is in # hexadecimal base. self.dlr_msg_id_bases = kwargs.get('dlr_msg_id_bases', 0) if self.dlr_msg_id_bases not in [0, 1, 2]: raise UnknownValue('Invalid dlr_msg_id_bases: %s' % self.dlr_msg_id_bases)
def route_routable(self, updated_request): try: # Do we have a hex-content ? if b'hex-content' not in updated_request.args: # Convert utf8 to GSM 03.38 if updated_request.args[b'coding'][0] == b'0': if isinstance(updated_request.args[b'content'][0], bytes): short_message = updated_request.args[b'content'][ 0].decode().encode('gsm0338', 'replace') else: short_message = updated_request.args[b'content'][ 0].encode('gsm0338', 'replace') updated_request.args[b'content'][0] = short_message else: # Otherwise forward it as is short_message = updated_request.args[b'content'][0] else: # Otherwise convert hex to bin short_message = hex2bin( updated_request.args[b'hex-content'][0]) # Authentication user = authenticate_user(updated_request.args[b'username'][0], updated_request.args[b'password'][0], self.RouterPB, self.stats, self.log) # Update CnxStatus user.getCnxStatus().httpapi['connects_count'] += 1 user.getCnxStatus().httpapi['submit_sm_request_count'] += 1 user.getCnxStatus().httpapi['last_activity_at'] = datetime.now() # Build SubmitSmPDU SubmitSmPDU = self.opFactory.SubmitSM( source_addr=None if b'from' not in updated_request.args else updated_request.args[b'from'][0], destination_addr=updated_request.args[b'to'][0], short_message=short_message, data_coding=int(updated_request.args[b'coding'][0]), custom_tlvs=updated_request.args[b'custom_tlvs'][0]) self.log.debug("Built base SubmitSmPDU: %s", SubmitSmPDU) # Make Credential validation v = HttpAPICredentialValidator('Send', user, updated_request, submit_sm=SubmitSmPDU) v.validate() # Update SubmitSmPDU by default values from user MtMessagingCredential SubmitSmPDU = v.updatePDUWithUserDefaults(SubmitSmPDU) # Prepare for interception then routing routedConnector = None # init routable = RoutableSubmitSm(SubmitSmPDU, user) self.log.debug("Built Routable %s for SubmitSmPDU: %s", routable, SubmitSmPDU) # Should we tag the routable ? tags = [] if b'tags' in updated_request.args: tags = updated_request.args[b'tags'][0].split(b',') for tag in tags: if isinstance(tag, bytes): routable.addTag(tag.decode()) else: routable.addTag(tag) self.log.debug('Tagged routable %s: +%s', routable, tag) # Intercept interceptor = self.RouterPB.getMTInterceptionTable( ).getInterceptorFor(routable) if interceptor is not None: self.log.debug( "RouterPB selected %s interceptor for this SubmitSmPDU", interceptor) if self.interceptorpb_client is None: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not set !") raise InterceptorNotSetError('InterceptorPB not set !') if not self.interceptorpb_client.isConnected: self.stats.inc('interceptor_error_count') self.log.error("InterceptorPB not connected !") raise InterceptorNotConnectedError( 'InterceptorPB not connected !') script = interceptor.getScript() self.log.debug("Interceptor script loaded: %s", script) # Run ! r = yield self.interceptorpb_client.run_script( script, routable) if isinstance(r, dict) and r['http_status'] != 200: self.stats.inc('interceptor_error_count') self.log.error( 'Interceptor script returned %s http_status error.', r['http_status']) raise InterceptorRunError( code=r['http_status'], message='Interception specific error code %s' % r['http_status']) elif isinstance(r, (str, bytes)): self.stats.inc('interceptor_count') routable = pickle.loads(r) else: self.stats.inc('interceptor_error_count') self.log.error( 'Failed running interception script, got the following return: %s', r) raise InterceptorRunError( message= 'Failed running interception script, check log for details' ) # Get the route route = self.RouterPB.getMTRoutingTable().getRouteFor(routable) if route is None: self.stats.inc('route_error_count') self.log.error( "No route matched from user %s for SubmitSmPDU: %s", user, routable.pdu) raise RouteNotFoundError("No route found") # Get connector from selected route self.log.debug("RouterPB selected %s route for this SubmitSmPDU", route) routedConnector = route.getConnector() # Is it a failover route ? then check for a bound connector, otherwise don't route # The failover route requires at least one connector to be up, no message enqueuing will # occur otherwise. if repr(route) == 'FailoverMTRoute': self.log.debug( 'Selected route is a failover, will ensure connector is bound:' ) while True: c = self.SMPPClientManagerPB.perspective_connector_details( routedConnector.cid) if c: self.log.debug('Connector [%s] is: %s', routedConnector.cid, c['session_state']) else: self.log.debug('Connector [%s] is not found', routedConnector.cid) if c and c['session_state'][:6] == 'BOUND_': # Choose this connector break else: # Check next connector, None if no more connectors are available routedConnector = route.getConnector() if routedConnector is None: break if routedConnector is None: self.stats.inc('route_error_count') self.log.error( "Failover route has no bound connector to handle SubmitSmPDU: %s", routable.pdu) raise ConnectorNotFoundError( "Failover route has no bound connectors") # Re-update SubmitSmPDU with parameters from the route's connector connector_config = self.SMPPClientManagerPB.perspective_connector_config( routedConnector.cid) if connector_config: connector_config = pickle.loads(connector_config) routable = update_submit_sm_pdu(routable=routable, config=connector_config) # Set a placeholder for any parameter update to be applied on the pdu(s) param_updates = {} # Set priority priority = 0 if b'priority' in updated_request.args: priority = int(updated_request.args[b'priority'][0]) param_updates['priority_flag'] = priority_flag_value_map[ priority] self.log.debug("SubmitSmPDU priority is set to %s", priority) # Set schedule_delivery_time if b'sdt' in updated_request.args: param_updates['schedule_delivery_time'] = parse( updated_request.args[b'sdt'][0]) self.log.debug( "SubmitSmPDU schedule_delivery_time is set to %s (%s)", routable.pdu.params['schedule_delivery_time'], updated_request.args[b'sdt'][0]) # Set validity_period if b'validity-period' in updated_request.args: delta = timedelta( minutes=int(updated_request.args[b'validity-period'][0])) param_updates['validity_period'] = datetime.today() + delta self.log.debug( "SubmitSmPDU validity_period is set to %s (+%s minutes)", routable.pdu.params['validity_period'], updated_request.args[b'validity-period'][0]) # Got any updates to apply on pdu(s) ? if len(param_updates) > 0: routable = update_submit_sm_pdu( routable=routable, config=param_updates, config_update_params=list(param_updates)) # Set DLR bit mask on the last pdu _last_pdu = routable.pdu while True: if hasattr(_last_pdu, 'nextPdu'): _last_pdu = _last_pdu.nextPdu else: break # DLR setting is clearly described in #107 _last_pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED) if updated_request.args[b'dlr'][0] == b'yes': _last_pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED) self.log.debug("SubmitSmPDU registered_delivery is set to %s", str(_last_pdu.params['registered_delivery'])) dlr_level = int(updated_request.args[b'dlr-level'][0]) if b'dlr-url' in updated_request.args: dlr_url = updated_request.args[b'dlr-url'][0] else: dlr_url = None if updated_request.args[b'dlr-level'][0] == b'1': dlr_level_text = 'SMS-C' elif updated_request.args[b'dlr-level'][0] == b'2': dlr_level_text = 'Terminal' else: dlr_level_text = 'All' dlr_method = updated_request.args[b'dlr-method'][0] else: dlr_url = None dlr_level = 0 dlr_level_text = 'No' dlr_method = None # QoS throttling if ( user.mt_credential.getQuota('http_throughput') and user.mt_credential.getQuota('http_throughput') >= 0 ) and user.getCnxStatus().httpapi['qos_last_submit_sm_at'] != 0: qos_throughput_second = 1 / float( user.mt_credential.getQuota('http_throughput')) qos_throughput_ysecond_td = timedelta( microseconds=qos_throughput_second * 1000000) qos_delay = datetime.now() - user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] if qos_delay < qos_throughput_ysecond_td: self.stats.inc('throughput_error_count') self.log.error( "QoS: submit_sm_event is faster (%s) than fixed throughput (%s), user:%s, rejecting message.", qos_delay, qos_throughput_ysecond_td, user) raise ThroughputExceededError("User throughput exceeded") user.getCnxStatus( ).httpapi['qos_last_submit_sm_at'] = datetime.now() # Get number of PDUs to be sent (for billing purpose) _pdu = routable.pdu submit_sm_count = 1 while hasattr(_pdu, 'nextPdu'): _pdu = _pdu.nextPdu submit_sm_count += 1 # Pre-sending submit_sm: Billing processing bill = route.getBillFor(user) self.log.debug( "SubmitSmBill [bid:%s] [ttlamounts:%s] generated for this SubmitSmPDU (x%s)", bill.bid, bill.getTotalAmounts(), submit_sm_count) charging_requirements = [] u_balance = user.mt_credential.getQuota('balance') u_subsm_count = user.mt_credential.getQuota('submit_sm_count') if u_balance is not None and bill.getTotalAmounts() > 0: # Ensure user have enough balance to pay submit_sm and submit_sm_resp charging_requirements.append({ 'condition': bill.getTotalAmounts() * submit_sm_count <= u_balance, 'error_message': 'Not enough balance (%s) for charging: %s' % (u_balance, bill.getTotalAmounts()) }) if u_subsm_count is not None: # Ensure user have enough submit_sm_count to to cover # the bill action (decrement_submit_sm_count) charging_requirements.append({ 'condition': bill.getAction('decrement_submit_sm_count') * submit_sm_count <= u_subsm_count, 'error_message': 'Not enough submit_sm_count (%s) for charging: %s' % (u_subsm_count, bill.getAction('decrement_submit_sm_count')) }) if self.RouterPB.chargeUserForSubmitSms( user, bill, submit_sm_count, charging_requirements) is None: self.stats.inc('charging_error_count') self.log.error( 'Charging user %s failed, [bid:%s] [ttlamounts:%s] SubmitSmPDU (x%s)', user, bill.bid, bill.getTotalAmounts(), submit_sm_count) raise ChargingError( 'Cannot charge submit_sm, check RouterPB log file for details' ) ######################################################## # Send SubmitSmPDU through smpp client manager PB server self.log.debug( "Connector '%s' is set to be a route for this SubmitSmPDU", routedConnector.cid) c = self.SMPPClientManagerPB.perspective_submit_sm( uid=user.uid, cid=routedConnector.cid, SubmitSmPDU=routable.pdu, submit_sm_bill=bill, priority=priority, pickled=False, dlr_url=dlr_url, dlr_level=dlr_level, dlr_method=dlr_method, dlr_connector=routedConnector.cid) # Build final response if not c.result: self.stats.inc('server_error_count') self.log.error('Failed to send SubmitSmPDU to [cid:%s]', routedConnector.cid) raise ServerError( 'Cannot send submit_sm, check SMPPClientManagerPB log file for details' ) else: self.stats.inc('success_count') self.stats.set('last_success_at', datetime.now()) self.log.debug('SubmitSmPDU sent to [cid:%s], result = %s', routedConnector.cid, c.result) response = {'return': c.result, 'status': 200} except HttpApiError as e: self.log.error("Error: %s", e) response = {'return': e.message, 'status': e.code} except Exception as e: self.log.error("Error: %s", e) response = {'return': "Unknown error: %s" % e, 'status': 500} raise finally: self.log.debug("Returning %s to %s.", response, updated_request.getClientIP()) updated_request.setResponseCode(response['status']) # Default return _return = 'Error "%s"' % response['return'] # Success return if response['status'] == 200 and routedConnector is not None: # Do not log text for privacy reasons # Added in #691 if self.config.log_privacy: logged_content = '** %s byte content **' % len( short_message) else: if isinstance(short_message, str): short_message = short_message.encode() logged_content = '%r' % re.sub(rb'[^\x20-\x7E]+', b'.', short_message) self.log.info( 'SMS-MT [uid:%s] [cid:%s] [msgid:%s] [prio:%s] [dlr:%s] [from:%s] [to:%s] [content:%s]', user.uid, routedConnector.cid, response['return'], priority, dlr_level_text, routable.pdu.params['source_addr'], updated_request.args[b'to'][0], logged_content) _return = 'Success "%s"' % response['return'] updated_request.write(_return.encode()) updated_request.finish()
"This script will enforce sending message while asking for DLR" from smpp.pdu.pdu_types import RegisteredDeliveryReceipt, RegisteredDelivery routable.pdu.params['registered_delivery'] = RegisteredDelivery( RegisteredDeliveryReceipt.SMSC_DELIVERY_RECEIPT_REQUESTED)
async def send_deliver_sm( self, source_addr: str, destination_addr: str, text: str, source_addr_npi: AddrNpi, dest_addr_npi: AddrNpi, source_addr_ton: AddrTon, dest_addr_ton: AddrTon, priority_flag: PriorityFlag = PriorityFlag.LEVEL_0, registered_delivery: RegisteredDelivery = None): if registered_delivery is None: registered_delivery = RegisteredDelivery( RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED) def try_to_encode(text: str, codec: str) -> Optional[bytes]: try: return text.encode(codec) except UnicodeEncodeError: return None codec_data_coding_map = [('ascii', DataCodingDefault.SMSC_DEFAULT_ALPHABET), ('latin-1', DataCodingDefault.LATIN_1), ('cyrillic', DataCodingDefault.CYRILLIC), ('utf-16be', DataCodingDefault.UCS2)] short_message = None data_coding = None for pair in codec_data_coding_map: short_message = try_to_encode(text=text, codec=pair[0]) if short_message: data_coding = pair[1] break if data_coding is None or short_message is None: raise Exception(f'Text {text} could not be encoded') deliver_sm_dict = { 'sequence_number': self.next_sequence_number(), 'service_type': None, 'source_addr_ton': source_addr_ton, 'source_addr_npi': source_addr_npi, 'source_addr': source_addr, 'dest_addr_ton': dest_addr_ton, 'dest_addr_npi': dest_addr_npi, 'destination_addr': destination_addr, 'esm_class': EsmClass(EsmClassMode.DEFAULT, EsmClassType.DEFAULT), 'protocol_id': None, 'priority_flag': priority_flag, 'registered_delivery': registered_delivery, 'replace_if_present_flag': ReplaceIfPresentFlag.DO_NOT_REPLACE, 'data_coding': DataCoding(scheme_data=data_coding), 'short_message': short_message, } max_size = 100 if len(short_message) <= max_size: deliver_sm = DeliverSM(**deliver_sm_dict) requests = [deliver_sm] else: requests = [] short_messages = [ short_message[x:x + max_size] for x in range(0, len(short_message), max_size) ] ref_num = self.next_ref_num() total_segments = len(short_messages) if total_segments > 255: raise Exception('Text is too long and it cannot be sent') for seq_num, trunk_short_message in enumerate(short_messages): deliver_sm_dict['short_message'] = trunk_short_message deliver_sm_dict['sequence_number'] = self.next_sequence_number( ) deliver_sm = DeliverSM(**deliver_sm_dict, sar_msg_ref_num=ref_num, sar_total_segments=total_segments, sar_segment_seqnum=seq_num + 1) requests.append(deliver_sm) self._send_requests(requests=requests, merge=True)