def do_bind_conversion_test(self, pduBindKlass, reqCommandIdHex, respCommandIdHex): reqPdu = pduBindKlass( 2, CommandStatus.ESME_ROK, system_id='test', password='******', system_type='OTA', interface_version=0x34, addr_ton=AddrTon.NATIONAL, addr_npi=AddrNpi.LAND_MOBILE, address_range='127.0.0.*', ) self.do_conversion_test( PDUEncoder(), reqPdu, b'0000002d%s00000000000000027465737400736563726574004f5441003402063132372e302e302e2a00' % reqCommandIdHex) respPdu = reqPdu.require_ack(1, CommandStatus.ESME_ROK, system_id='TSI7588', sc_interface_version=0x34) self.do_conversion_test( PDUEncoder(), respPdu, b'0000001d%s000000000000000154534937353838000210000134' % respCommandIdHex)
class BlackHoleSMSC( protocol.Protocol ): responseMap = {} def debug_func(self, stack): self.log.debug('-start---------------------------------------------------------------------') self.log.debug(stack[1]) self.log.debug('-stop----------------------------------------------------------------------') def __init__( self ): self.log = logging.getLogger(LOG_CATEGORY) self.debug_func(inspect.stack()) self.recvBuffer = "" self.lastSeqNum = 0 self.encoder = PDUEncoder() def dataReceived( self, data ): self.debug_func(inspect.stack()) self.recvBuffer = self.recvBuffer + data while len( self.recvBuffer ) > 3: ( length, ) = struct.unpack( '!L', self.recvBuffer[:4] ) if len( self.recvBuffer ) < length: break message = self.recvBuffer[:length] self.recvBuffer = self.recvBuffer[length:] self.rawMessageReceived( message ) def rawMessageReceived( self, message ): self.debug_func(inspect.stack()) return self.PDUReceived( self.encoder.decode( StringIO.StringIO(message) ) ) def PDUReceived( self, pdu ): self.debug_func(inspect.stack()) if pdu.__class__ in self.responseMap: self.responseMap[pdu.__class__](pdu) def sendSuccessResponse(self, reqPDU): self.debug_func(inspect.stack()) self.sendResponse(reqPDU, CommandStatus.ESME_ROK) def sendResponse(self, reqPDU, status): self.debug_func(inspect.stack()) respPDU = reqPDU.requireAck(reqPDU.seqNum) respPDU.status = status self.sendPDU(respPDU) def sendPDU(self, pdu): self.debug_func(inspect.stack()) if isinstance(pdu, PDURequest) and pdu.seqNum is None: self.lastSeqNum += 1 pdu.seqNum = self.lastSeqNum # self.log.debug("Sending PDU: %s" % pdu) encoded = self.encoder.encode(pdu) # self.log.debug("Sending data [%s]" % binascii.b2a_hex(encoded)) self.transport.write( encoded )
def test_BindTransceiverResp_error_has_no_body_status_set_later(self): hex = b'00000010800000090000000e00000d80' pdu = BindTransceiverResp(3456, system_id="XYZ") pdu.status = CommandStatus.ESME_RINVPASWD # Even though the system_id param was set, it will not be encoded self.do_encode_test(PDUEncoder(), pdu, hex) # It will decode with no params set pduExpected = BindTransceiverResp(3456, status=CommandStatus.ESME_RINVPASWD) self.assertEqual(0, len(pduExpected.params)) self.do_decode_test(PDUEncoder(), pduExpected, hex)
def _send_requests(self, requests: List[PDURequest], merge=True): if not self.is_bound: raise Exception('Cannot send request to unbound client') encoder = PDUEncoder() if merge: buffer_list = [encoder.encode(pdu) for pdu in requests] self._transport.write(b''.join(buffer_list)) else: for pdu in requests: self._transport.write(encoder.encode(pdu))
def __init__(self): self.log = logging.getLogger(LOG_CATEGORY) self.recvBuffer = "" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.lastSeqNum = 0 self.dataRequestHandler = None self.alertNotificationHandler = None self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred()
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 __init__(self): self.recvBuffer = "" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.dataRequestHandler = None self.lastSeqNum = 0 self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred() # Overriden in tests self.callLater = reactor.callLater self.port = None
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 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' )
class BlackHoleSMSC(protocol.Protocol): responseMap = {} def __init__(self): self.log = logging.getLogger(LOG_CATEGORY) self.recvBuffer = "" self.lastSeqNum = 0 self.encoder = PDUEncoder() def dataReceived(self, data): self.recvBuffer = self.recvBuffer + data while len(self.recvBuffer) > 3: (length, ) = struct.unpack('!L', self.recvBuffer[:4]) if len(self.recvBuffer) < length: break message = self.recvBuffer[:length] self.recvBuffer = self.recvBuffer[length:] self.rawMessageReceived(message) def rawMessageReceived(self, message): return self.PDUReceived(self.encoder.decode( StringIO.StringIO(message))) def PDUReceived(self, pdu): if pdu.__class__ in self.responseMap: self.responseMap[pdu.__class__](pdu) def sendSuccessResponse(self, reqPDU): self.sendResponse(reqPDU, CommandStatus.ESME_ROK) def sendSuccessResponseWithStatus(self, reqPDU, status): self.sendResponse(reqPDU, CommandStatus.ESME_ROK) def sendResponse(self, reqPDU, status): respPDU = reqPDU.requireAck(reqPDU.seqNum, status=status) self.sendPDU(respPDU) def sendPDU(self, pdu): if isinstance(pdu, PDURequest) and pdu.seqNum is None: self.lastSeqNum += 1 pdu.seqNum = self.lastSeqNum # self.log.debug("Sending PDU: %s" % pdu) encoded = self.encoder.encode(pdu) # self.log.debug("Sending data [%s]" % binascii.b2a_hex(encoded)) self.transport.write(encoded)
def __init__(self): self.recvBuffer = b"" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.dataRequestHandler = None self.lastSeqNum = 0 self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred() # Overriden in tests #pylint: disable=no-member self.callLater = reactor.callLater self.port = None self.log = logging.getLogger(LOG_CATEGORY)
def test_QuerySMResp_conversion(self): pdu = QuerySMResp( message_id='Smsc2003', final_date=None, message_state=MessageState.UNKNOWN, error_code=None, ) self.do_conversion_test( PDUEncoder(), pdu, b'0000001c800000030000000000000000536d73633230303300000700')
def __init__( self ): self.log = logging.getLogger(LOG_CATEGORY) self.recvBuffer = "" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.lastSeqNum = 0 self.dataRequestHandler = None self.alertNotificationHandler = None self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred()
def test_AlertNotification_conversion(self): pdu = AlertNotification( source_addr_ton=AddrTon.NATIONAL, source_addr_npi=AddrNpi.ISDN, source_addr= 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', esme_addr_ton=AddrTon.INTERNATIONAL, esme_addr_npi=AddrNpi.LAND_MOBILE, esme_addr= 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY', ms_availability_status=MsAvailabilityStatus.DENIED, ) self.do_conversion_test( PDUEncoder(), pdu, b'0000008900000102000000000000000002015858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858585858580001065959595959595959595959595959595959595959595959595959595959595959595959595959595959595959595959595959595959595959000422000101' )
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 getPDU(self, hexStr): return PDUEncoder().decode(StringIO.StringIO(binascii.a2b_hex(hexStr)))
def _pdu2bin(self, pdu): return PDUEncoder().encode(pdu)
def _bin2pdu(self, bindata): io_pdu = StringIO(bindata) return PDUEncoder().decode(io_pdu)
class SMPPClientProtocol(protocol.Protocol): """Short Message Peer to Peer Protocol v3.4 implementing ESME (client)""" version = 0x34 def __init__(self): self.log = logging.getLogger(LOG_CATEGORY) self.recvBuffer = "" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.lastSeqNum = 0 self.dataRequestHandler = None self.alertNotificationHandler = None self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred() def config(self): return self.factory.getConfig() def connectionMade(self): """When TCP connection is made """ protocol.Protocol.connectionMade(self) self.sessionState = SMPPSessionStates.OPEN self.log.warning("Connection established") def connectionLost(self, reason): protocol.Protocol.connectionLost(self, reason) self.log.warning("Disconnected: %s" % reason) self.sessionState = SMPPSessionStates.NONE self.cancelEnquireLinkTimer() self.cancelInactivityTimer() self.disconnectedDeferred.callback(None) def dataReceived(self, data): """ Looks for a full PDU (protocol data unit) and passes it from rawMessageReceived. """ # if self.log.isEnabledFor(logging.DEBUG): # self.log.debug("Received data [%s]" % binascii.b2a_hex(data)) self.recvBuffer = self.recvBuffer + data while True: if self.connectionCorrupted: return msg = self.readMessage() if msg is None: break self.endPDURead() self.rawMessageReceived(msg) if len(self.recvBuffer) > 0: self.incompletePDURead() def incompletePDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): return self.pduReadTimer = reactor.callLater(self.config().pduReadTimerSecs, self.onPDUReadTimeout) def endPDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): self.pduReadTimer.cancel() def readMessage(self): pduLen = self.getMessageLength() if pduLen is None: return None return self.getMessage(pduLen) def getMessageLength(self): if len(self.recvBuffer) < 4: return None return struct.unpack('!L', self.recvBuffer[:4])[0] def getMessage(self, pduLen): if len(self.recvBuffer) < pduLen: return None message = self.recvBuffer[:pduLen] self.recvBuffer = self.recvBuffer[pduLen:] return message def corruptDataRecvd(self, status=CommandStatus.ESME_RINVCMDLEN): self.sendPDU(GenericNack(status=status)) self.onCorruptConnection() def onCorruptConnection(self): """ Once the connection is corrupt, the PDU boundaries are lost and it's impossible to continue processing messages. - Set a flag to indicate corrupt connection - no more parse attempts should be made for inbound data - no more outbound requests should be attempted (they should errback immediately) - Cancel outstanding outbound requests (which have not yet been ack'ed) (removed from the list and errback called) - Shutdown """ self.log.critical("Connection is corrupt!!! Shutting down...") self.connectionCorrupted = True self.cancelOutboundTransactions(SMPPClientConnectionCorruptedError()) self.shutdown() def getHeader(self, message): try: return self.encoder.decodeHeader( StringIO.StringIO(message[:self.encoder.HEADER_LEN])) except: return {} def onPDUReadTimeout(self): self.log.critical( 'PDU read timed out. Buffer is now considered corrupt') self.corruptDataRecvd() def rawMessageReceived(self, message): """Called once a PDU (protocol data unit) boundary is identified. Creates an SMPP PDU class from the data and calls PDUReceived dispatcher """ pdu = None try: pdu = self.encoder.decode(StringIO.StringIO(message)) except PDUCorruptError, e: self.log.exception(e) self.log.critical("Received corrupt PDU %s" % binascii.b2a_hex(message)) self.corruptDataRecvd(status=e.status) except PDUParseError, e: self.log.exception(e) self.log.critical("Received unparsable PDU %s" % binascii.b2a_hex(message)) header = self.getHeader(message) seqNum = header.get('sequence_number', None) commandId = header.get('command_id', None) self.sendPDU( getPDUClass(commandId).requireAck(seqNum=seqNum, status=e.status))
def __init__(self): self.log = logging.getLogger(LOG_CATEGORY) self.recvBuffer = "" self.lastSeqNum = 0 self.encoder = PDUEncoder()
class SMPPProtocolBase(protocol.Protocol): """Short Message Peer to Peer Protocol v3.4 implementing ESME (client)""" version = 0x34 def __init__(self): self.recvBuffer = "" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.dataRequestHandler = None self.lastSeqNum = 0 self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred() # Overriden in tests self.callLater = reactor.callLater self.port = None def config(self): return self.factory.getConfig() def connectionMade(self): """When TCP connection is made """ protocol.Protocol.connectionMade(self) self.port = self.transport.getHost().port # Start the inactivity timer the connection is dropped if we receive no data self.activateInactivityTimer() self.sessionState = SMPPSessionStates.OPEN self.log.warning("SMPP connection established from %s to port %s", self.transport.getPeer().host, self.port) def connectionLost(self, reason): protocol.Protocol.connectionLost(self, reason) self.log.warning("SMPP %s disconnected from port %s: %s", self.transport.getPeer().host, self.port, reason) self.sessionState = SMPPSessionStates.NONE self.cancelEnquireLinkTimer() self.cancelInactivityTimer() self.disconnectedDeferred.callback(None) def dataReceived(self, data): """ Looks for a full PDU (protocol data unit) and passes it from rawMessageReceived. """ # if self.log.isEnabledFor(logging.DEBUG): # self.log.debug("Received data [%s]" % _safelylogOutPdu(data)) self.recvBuffer = self.recvBuffer + data while True: if self.connectionCorrupted: return msg = self.readMessage() if msg is None: break self.endPDURead() self.rawMessageReceived(msg) if len(self.recvBuffer) > 0: self.incompletePDURead() def incompletePDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): return self.pduReadTimer = self.callLater(self.config().pduReadTimerSecs, self.onPDUReadTimeout) def endPDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): self.pduReadTimer.cancel() def readMessage(self): pduLen = self.getMessageLength() if pduLen is None: return None return self.getMessage(pduLen) def getMessageLength(self): if len(self.recvBuffer) < 4: return None return struct.unpack('!L', self.recvBuffer[:4])[0] def getMessage(self, pduLen): if len(self.recvBuffer) < pduLen: return None message = self.recvBuffer[:pduLen] self.recvBuffer = self.recvBuffer[pduLen:] return message def corruptDataRecvd(self, status=CommandStatus.ESME_RINVCMDLEN): self.sendPDU(GenericNack(status=status)) self.onCorruptConnection() def onCorruptConnection(self): """ Once the connection is corrupt, the PDU boundaries are lost and it's impossible to continue processing messages. - Set a flag to indicate corrupt connection - no more parse attempts should be made for inbound data - no more outbound requests should be attempted (they should errback immediately) - Cancel outstanding outbound requests (which have not yet been ack'ed) (removed from the list and errback called) - Shutdown """ self.log.critical("Connection is corrupt!!! Shutting down...") self.connectionCorrupted = True self.cancelOutboundTransactions(SMPPClientConnectionCorruptedError()) self.shutdown() def getHeader(self, message): try: return self.encoder.decodeHeader(StringIO.StringIO(message[:self.encoder.HEADER_LEN])) except: return {} def onPDUReadTimeout(self): self.log.critical('PDU read timed out. Buffer is now considered corrupt') self.corruptDataRecvd() def rawMessageReceived(self, message): """Called once a PDU (protocol data unit) boundary is identified. Creates an SMPP PDU class from the data and calls PDUReceived dispatcher """ pdu = None try: pdu = self.encoder.decode(StringIO.StringIO(message)) except PDUCorruptError as e: self.log.exception(e) self.log.critical("Received corrupt PDU %s" % _safelylogOutPdu(message)) self.corruptDataRecvd(status=e.status) except PDUParseError as e: self.log.exception(e) self.log.critical("Received unparsable PDU %s" % _safelylogOutPdu(message)) header = self.getHeader(message) seqNum = header.get('sequence_number', None) commandId = header.get('command_id', None) self.sendPDU(getPDUClass(commandId).requireAck(seqNum=seqNum, status=e.status)) else: self.PDUReceived(pdu) def PDUReceived(self, pdu): """Dispatches incoming PDUs """ if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Received PDU: %s" % pdu) encoded = self.encoder.encode(pdu) if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Receiving data [%s]" % _safelylogOutPdu(encoded)) # Signal SMPP operation self.onSMPPOperation() if isinstance(pdu, PDURequest): self.PDURequestReceived(pdu) elif isinstance(pdu, PDUResponse): self.PDUResponseReceived(pdu) else: getattr(self, "onPDU_%s" % str(pdu.id))(pdu) def PDURequestReceived(self, reqPDU): """Handle incoming request PDUs """ if isinstance(reqPDU, PDUDataRequest): self.PDUDataRequestReceived(reqPDU) return getattr(self, "onPDURequest_%s" % str(reqPDU.id))(reqPDU) def onPDURequest_enquire_link(self, reqPDU): self.sendResponse(reqPDU) def onPDURequest_unbind(self, reqPDU): # Allow no more outbound data requests # Accept no more inbound requests self.sessionState = SMPPSessionStates.UNBIND_RECEIVED self.cancelEnquireLinkTimer() # Cancel outbound requests self.cancelOutboundTransactions(SMPPClientSessionStateError('Unbind received')) # Wait for inbound requests to finish then ack and disconnect self.finishInboundTxns().addCallback(lambda r: (self.sendResponse(reqPDU) or True) and self.disconnect()) def sendResponse(self, reqPDU, status=CommandStatus.ESME_ROK, **params): self.sendPDU(reqPDU.requireAck(reqPDU.seqNum, status, **params)) def PDUDataRequestReceived(self, reqPDU): if self.sessionState == SMPPSessionStates.UNBIND_PENDING: self.log.info("Unbind is pending...Ignoring data request PDU %s" % reqPDU) return if not self.isBound(): errMsg = 'Received data request when not bound %s' % reqPDU self.cancelOutboundTransactions(SessionStateError(errMsg, CommandStatus.ESME_RINVBNDSTS)) return self.fatalErrorOnRequest(reqPDU, errMsg, CommandStatus.ESME_RINVBNDSTS) if self.dataRequestHandler is None: return self.fatalErrorOnRequest(reqPDU, 'Missing dataRequestHandler', CommandStatus.ESME_RX_T_APPN) self.doPDURequest(reqPDU, self.dataRequestHandler) def fatalErrorOnRequest(self, reqPDU, errMsg, status): self.log.critical(errMsg) self.sendResponse(reqPDU, status) self.shutdown() def doPDURequest(self, reqPDU, handler): self.startInboundTransaction(reqPDU) handlerCall = defer.maybeDeferred(handler, self, reqPDU) handlerCall.addCallback(self.PDURequestSucceeded, reqPDU) handlerCall.addErrback(self.PDURequestFailed, reqPDU) handlerCall.addBoth(self.PDURequestFinished, reqPDU) def PDURequestSucceeded(self, dataHdlrResp, reqPDU): if reqPDU.requireAck: status = CommandStatus.ESME_ROK params = {} if dataHdlrResp: if dataHdlrResp in CommandStatus: status = dataHdlrResp elif isinstance(dataHdlrResp, DataHandlerResponse): status = dataHdlrResp.status params = dataHdlrResp.params else: self.log.critical("Invalid response type returned from data handler %s" % type(dataHdlrResp)) status = CommandStatus.ESME_RX_T_APPN self.shutdown() self.sendResponse(reqPDU, status, **params) def PDURequestFailed(self, error, reqPDU): if error.check(SMPPProtocolError): # Get the original error try: error.raiseException() except SMPPProtocolError as validation_error: self.log.debug("Application raised error '%s', forwarding to client. Inbound PDU was [%s], hex[%s]" % ( validation_error, reqPDU, _safelylogOutPdu(self.encoder.encode(reqPDU)))) return_cmd_status = validation_error.commandStatusName shutdown = False else: self.log.critical('Exception raised handling inbound PDU [%s] hex[%s]: %s' % ( reqPDU, _safelylogOutPdu(self.encoder.encode(reqPDU)), error)) return_cmd_status = CommandStatus.ESME_RX_T_APPN shutdown = True if reqPDU.requireAck: self.sendResponse(reqPDU, return_cmd_status) if shutdown: self.shutdown() def PDURequestFinished(self, result, reqPDU): self.endInboundTransaction(reqPDU) return result def finishTxns(self): return defer.DeferredList([self.finishInboundTxns(), self.finishOutboundTxns()]) def finishInboundTxns(self): return defer.DeferredList(self.inTxns.values()) def finishOutboundTxns(self): return defer.DeferredList([txn.ackDeferred for txn in self.outTxns.values()]) def PDUResponseReceived(self, pdu): """Handle incoming response PDUs """ if isinstance(pdu, GenericNack): self.log.critical("Recevied generic_nack %s" % pdu) if pdu.seqNum is None: self.onCorruptConnection() return if pdu.seqNum not in self.outTxns: self.log.critical('Response PDU received with unknown outbound transaction sequence number %s' % pdu) return self.endOutboundTransaction(pdu) def sendPDU(self, pdu): """Send a SMPP PDU """ if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Sending PDU: %s" % pdu) encoded = self.encoder.encode(pdu) if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Sending data [%s]" % _safelylogOutPdu(encoded)) self.transport.write(encoded) self.onSMPPOperation() def sendBindRequest(self, pdu): return self.sendRequest(pdu, self.config().sessionInitTimerSecs) def sendRequest(self, pdu, timeout): return defer.maybeDeferred(self.doSendRequest, pdu, timeout) def doSendRequest(self, pdu, timeout): if self.connectionCorrupted: raise SMPPClientConnectionCorruptedError() if not isinstance(pdu, PDURequest) or pdu.requireAck is None: raise SMPPClientError("Invalid PDU to send: %s" % pdu) pdu.seqNum = self.claimSeqNum() self.sendPDU(pdu) return self.startOutboundTransaction(pdu, timeout) def onSMPPOperation(self): """Called whenever an SMPP PDU is sent or received """ if self.isBound(): self.activateEnquireLinkTimer() self.activateInactivityTimer() def activateEnquireLinkTimer(self): if self.enquireLinkTimer and self.enquireLinkTimer.active(): self.enquireLinkTimer.reset(self.config().enquireLinkTimerSecs) elif self.config().enquireLinkTimerSecs: self.enquireLinkTimer = self.callLater(self.config().enquireLinkTimerSecs, self.enquireLinkTimerExpired) def activateInactivityTimer(self): if self.inactivityTimer and self.inactivityTimer.active(): self.inactivityTimer.reset(self.config().inactivityTimerSecs) elif self.config().inactivityTimerSecs: self.inactivityTimer = self.callLater(self.config().inactivityTimerSecs, self.inactivityTimerExpired) def cancelEnquireLinkTimer(self): if self.enquireLinkTimer and self.enquireLinkTimer.active(): self.enquireLinkTimer.cancel() self.enquireLinkTimer = None def cancelInactivityTimer(self): if self.inactivityTimer and self.inactivityTimer.active(): self.inactivityTimer.cancel() self.inactivityTimer = None def enquireLinkTimerExpired(self): txn = self.sendRequest(EnquireLink(), self.config().responseTimerSecs) txn.addErrback(self.enquireLinkErr) def enquireLinkErr(self, failure): # Unbinding already anyway. No need to raise another error failure.trap(SMPPError) def inactivityTimerExpired(self): self.log.critical("Inactivity timer expired...shutting down") self.shutdown() def isBound(self): return self.sessionState in ( SMPPSessionStates.BOUND_TX, SMPPSessionStates.BOUND_RX, SMPPSessionStates.BOUND_TRX) def shutdown(self): """ Unbind if appropriate and disconnect """ if self.isBound() and not self.connectionCorrupted: self.log.warning("Shutdown requested...unbinding") self.unbind().addBoth(lambda result: self.disconnect()) elif self.sessionState not in (SMPPSessionStates.UNBIND_RECEIVED, SMPPSessionStates.UNBIND_PENDING): self.log.warning("Shutdown requested...disconnecting") self.disconnect() else: self.log.debug("Shutdown already in progress") def startInboundTransaction(self, reqPDU): if reqPDU.seqNum in self.inTxns: raise SMPPProtocolError('Duplicate message id [%s] received. Already in progess.' % reqPDU.seqNum, CommandStatus.ESME_RUNKNOWNERR) txnDeferred = defer.Deferred() self.inTxns[reqPDU.seqNum] = txnDeferred self.log.debug("Inbound transaction started with message id %s" % reqPDU.seqNum) return txnDeferred def endInboundTransaction(self, reqPDU): if not reqPDU.seqNum in self.inTxns: raise ValueError('Unknown inbound sequence number in transaction for request PDU %s' % reqPDU) self.log.debug("Inbound transaction finished with message id %s" % reqPDU.seqNum) self.inTxns[reqPDU.seqNum].callback(reqPDU) del self.inTxns[reqPDU.seqNum] def startOutboundTransaction(self, reqPDU, timeout): if reqPDU.seqNum in self.outTxns: raise ValueError('Seq number [%s] is already in progess.' % reqPDU.seqNum) # Create callback deferred ackDeferred = defer.Deferred() # Create response timer timer = self.callLater(timeout, self.onResponseTimeout, reqPDU, timeout) # Save transaction self.outTxns[reqPDU.seqNum] = SMPPOutboundTxn(reqPDU, timer, ackDeferred) self.log.debug("Outbound transaction started with message id %s" % reqPDU.seqNum) return ackDeferred def closeOutboundTransaction(self, seqNum): self.log.debug("Outbound transaction finished with message id %s" % seqNum) txn = self.outTxns[seqNum] # Remove txn del self.outTxns[seqNum] # Cancel response timer if txn.timer.active(): txn.timer.cancel() return txn def endOutboundTransaction(self, respPDU): txn = self.closeOutboundTransaction(respPDU.seqNum) if respPDU.status == CommandStatus.ESME_ROK: if not isinstance(respPDU, txn.request.requireAck): txn.ackDeferred.errback(SMPPProtocolError( "Invalid PDU response type [%s] returned for request type [%s]" % ( type(respPDU), type(txn.request)))) return # Do callback txn.ackDeferred.callback(SMPPOutboundTxnResult(self, txn.request, respPDU)) return if isinstance(respPDU, GenericNack): txn.ackDeferred.errback(SMPPGenericNackTransactionError(respPDU, txn.request)) return errCode = respPDU.status txn.ackDeferred.errback(SMPPTransactionError(respPDU, txn.request)) def endOutboundTransactionErr(self, reqPDU, error): self.log.error(error) txn = self.closeOutboundTransaction(reqPDU.seqNum) # Do errback txn.ackDeferred.errback(error) def cancelOutboundTransactions(self, error): for txn in self.outTxns.values(): self.endOutboundTransactionErr(txn.request, error) def onResponseTimeout(self, reqPDU, timeout): errMsg = 'Request timed out after %s secs: %s' % (timeout, reqPDU) self.endOutboundTransactionErr(reqPDU, SMPPRequestTimoutError(errMsg)) self.shutdown() def claimSeqNum(self): self.lastSeqNum += 1 return self.lastSeqNum def unbindSucceeded(self, result): self.sessionState = SMPPSessionStates.UNBOUND self.log.warning("Unbind succeeded") return result def unbindFailed(self, reason): self.log.error("Unbind failed [%s]. Disconnecting..." % reason) self.disconnect() if reason.check(SMPPRequestTimoutError): raise SMPPSessionInitTimoutError(str(reason)) return reason def unbindAfterInProgressTxnsFinished(self, result, unbindDeferred): self.log.warning('Issuing unbind request') self.sendBindRequest(Unbind()).addCallbacks(self.unbindSucceeded, self.unbindFailed).chainDeferred( unbindDeferred) ############################################################################ # Public command functions ############################################################################ def unbind(self): """Unbind from SMSC Result is a Deferred object """ if not self.isBound(): return defer.fail( SMPPClientSessionStateError('unbind called with illegal session state: %s' % self.sessionState)) self.cancelEnquireLinkTimer() self.log.info('Waiting for in-progress transactions to finish...') # Signal that # - no new data requests should be sent # - no new incoming data requests should be accepted self.sessionState = SMPPSessionStates.UNBIND_PENDING unbindDeferred = defer.Deferred() # Wait for any in-progress txns to finish self.finishTxns().addCallback(self.unbindAfterInProgressTxnsFinished, unbindDeferred) # Result is the deferred for the unbind txn return unbindDeferred def unbindAndDisconnect(self): """Unbind from SMSC and disconnect Result is a Deferred object """ return self.unbind().addBoth(lambda result: self.disconnect()) def disconnect(self): """Disconnect from SMSC """ if self.isBound(): self.log.warning("Disconnecting while bound to SMSC...") else: self.log.warning("Disconnecting...") self.sessionState = SMPPSessionStates.UNBOUND self.transport.loseConnection() def getDisconnectedDeferred(self): """Get a Deferred so you can be notified on disconnect """ return self.disconnectedDeferred def sendDataRequest(self, pdu): """Send a SMPP Request Message Argument is an SMPP PDUDataRequest (protocol data unit). Result is a Deferred object """ if not isinstance(pdu, PDUDataRequest): return defer.fail(SMPPClientError("Invalid PDU passed to sendDataRequest(): %s" % pdu)) if not self.isBound(): return defer.fail(SMPPClientSessionStateError('Not bound')) return self.sendRequest(pdu, self.config().responseTimerSecs)
source_addr_npi=AddrNpi.UNKNOWN, source_addr='sarafu', dest_addr_ton=AddrTon.INTERNATIONAL, dest_addr_npi=AddrNpi.ISDN, destination_addr='07727', 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='HELLO', ) print "PDU: %s" % pdu binary = PDUEncoder().encode(pdu) hexStr = binascii.b2a_hex(binary) print "HEX: %s" % hexStr # r may represent request hex = '0000004d00000005000000009f88f12441575342440001013136353035353531323334000101313737333535353430373000000000000000000300117468657265206973206e6f2073706f6f6e' binary = binascii.a2b_hex(hex) file = StringIO.StringIO(binary) pdu = PDUEncoder().decode(file) print "PDU: %s" % pdu
def test_decode_bad_message_ends_in_middle_of_option(self): self.do_decode_corrupt_data_error_test( PDUEncoder().decode, CommandStatus.ESME_RINVMSGLEN, b'0000001b8000000900000000000000015453493735383800021000')
def test_SubmitSMResp_error_has_no_body(self): pdu = SubmitSMResp(1234, status=CommandStatus.ESME_RMSGQFUL) self.assertTrue(len(SubmitSMResp.mandatory_params) > 0) self.assertEqual(0, len(pdu.params)) self.do_conversion_test(PDUEncoder(), pdu, b'000000108000000400000014000004d2')
async def handle_data_received(self, data: bytes): self.app.logger.debug(f'Received {len(data)} bytes.') self.app.logger.debug(data) file = io.BytesIO(data) pdu = PDUEncoder().decode(file) self.app.logger.debug(f'Command received: {pdu.command_id}') if pdu.command_id == CommandId.submit_sm: submit_sm = SubmitSM(sequence_number=pdu.sequence_number, **pdu.params) sms = SMStringEncoder().decode_SM(submit_sm).str self._send_response( submit_sm.require_ack( sequence_number=submit_sm.sequence_number)) # Check if the message is only a part from a series while (submit_sm.params.get('more_messages_to_send') == MoreMessagesToSend.MORE_MESSAGES): pdu = PDUEncoder().decode(file) if pdu.command_id != CommandId.submit_sm: raise Exception( f'Received {pdu.command_id} instead ' f'of {CommandId.submit_sm} for concatenated messages') submit_sm = SubmitSM(sequence_number=pdu.sequence_number, **pdu.params) sms += SMStringEncoder().decode_SM(submit_sm).str self._send_response( submit_sm.require_ack( sequence_number=submit_sm.sequence_number)) await self.app.handle_sms_received( client=self._client, source_number=submit_sm.params['source_addr'], dest_number=submit_sm.params['destination_addr'], text=sms) elif pdu.command_id == CommandId.bind_transceiver: request = BindTransceiver(sequence_number=pdu.sequence_number, **pdu.params) if self.is_bound: smpp_status = CommandStatus.ESME_RALYBND else: client = SmppClient( protocol=self, system_id=request.params['system_id'], password=request.params['password'], system_type=request.params['system_type'], interface_version=request.params['interface_version'], addr_ton=request.params['addr_ton'], addr_npi=request.params['addr_npi']) self._client = client try: client = await self.app.handle_bound_client(client=client) except Exception as e: self.app.logger.error( f'Exception in handle_bound_client: {e}') smpp_status = CommandStatus.ESME_RBINDFAIL else: if client: self.is_bound = True smpp_status = CommandStatus.ESME_ROK else: self.is_bound = False # Generic bind error smpp_status = CommandStatus.ESME_RBINDFAIL resp = BindTransceiverResp(sequence_number=request.sequence_number, status=smpp_status, system_id=self.app.name) self._send_response(resp) elif pdu.command_id == CommandId.unbind: self.is_bound = False request = Unbind(sequence_number=pdu.sequence_number, **pdu.params) resp = UnbindResp(sequence_number=request.sequence_number) self._send_response(resp) await self.app.handle_unbound_client(self._client) else: await self.request_handler(pdu)
def __init__( self ): self.log = logging.getLogger(LOG_CATEGORY) self.debug_func(inspect.stack()) self.recvBuffer = "" self.lastSeqNum = 0 self.encoder = PDUEncoder()
class SMPPClientProtocol( protocol.Protocol ): """Short Message Peer to Peer Protocol v3.4 implementing ESME (client)""" version = 0x34 def __init__( self ): self.log = logging.getLogger(LOG_CATEGORY) self.recvBuffer = "" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.lastSeqNum = 0 self.dataRequestHandler = None self.alertNotificationHandler = None self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred() def config(self): return self.factory.getConfig() def connectionMade(self): """When TCP connection is made """ protocol.Protocol.connectionMade(self) self.sessionState = SMPPSessionStates.OPEN self.log.warning("Connection established") def connectionLost( self, reason ): protocol.Protocol.connectionLost( self, reason ) self.log.warning("Disconnected: %s" % reason) self.sessionState = SMPPSessionStates.NONE self.cancelEnquireLinkTimer() self.cancelInactivityTimer() self.disconnectedDeferred.callback(None) def dataReceived( self, data ): """ Looks for a full PDU (protocol data unit) and passes it from rawMessageReceived. """ # if self.log.isEnabledFor(logging.DEBUG): # self.log.debug("Received data [%s]" % binascii.b2a_hex(data)) self.recvBuffer = self.recvBuffer + data while True: if self.connectionCorrupted: return msg = self.readMessage() if msg is None: break self.endPDURead() self.rawMessageReceived(msg) if len(self.recvBuffer) > 0: self.incompletePDURead() def incompletePDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): return self.pduReadTimer = reactor.callLater(self.config().pduReadTimerSecs, self.onPDUReadTimeout) def endPDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): self.pduReadTimer.cancel() def readMessage(self): pduLen = self.getMessageLength() if pduLen is None: return None return self.getMessage(pduLen) def getMessageLength(self): if len(self.recvBuffer) < 4: return None return struct.unpack('!L', self.recvBuffer[:4])[0] def getMessage(self, pduLen): if len(self.recvBuffer) < pduLen: return None message = self.recvBuffer[:pduLen] self.recvBuffer = self.recvBuffer[pduLen:] return message def corruptDataRecvd(self, status=CommandStatus.ESME_RINVCMDLEN): self.sendPDU(GenericNack(status=status)) self.onCorruptConnection() def onCorruptConnection(self): """ Once the connection is corrupt, the PDU boundaries are lost and it's impossible to continue processing messages. - Set a flag to indicate corrupt connection - no more parse attempts should be made for inbound data - no more outbound requests should be attempted (they should errback immediately) - Cancel outstanding outbound requests (which have not yet been ack'ed) (removed from the list and errback called) - Shutdown """ self.log.critical("Connection is corrupt!!! Shutting down...") self.connectionCorrupted = True self.cancelOutboundTransactions(SMPPClientConnectionCorruptedError()) self.shutdown() def getHeader(self, message): try: return self.encoder.decodeHeader(StringIO.StringIO(message[:self.encoder.HEADER_LEN])) except: return {} def onPDUReadTimeout(self): self.log.critical('PDU read timed out. Buffer is now considered corrupt') self.corruptDataRecvd() def rawMessageReceived( self, message ): """Called once a PDU (protocol data unit) boundary is identified. Creates an SMPP PDU class from the data and calls PDUReceived dispatcher """ pdu = None try: pdu = self.encoder.decode(StringIO.StringIO(message)) except PDUCorruptError, e: self.log.exception(e) self.log.critical("Received corrupt PDU %s" % binascii.b2a_hex(message)) self.corruptDataRecvd(status=e.status) except PDUParseError, e: self.log.exception(e) self.log.critical("Received unparsable PDU %s" % binascii.b2a_hex(message)) header = self.getHeader(message) seqNum = header.get('sequence_number', None) commandId = header.get('command_id', None) self.sendPDU(getPDUClass(commandId).requireAck(seqNum=seqNum, status=e.status))
class SMPPProtocolBase(Protocol): """Short Message Peer to Peer Protocol v3.4 implementing ESME (client)""" version = 0x34 def __init__(self): self.recvBuffer = b"" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None self.inactivityTimer = None self.dataRequestHandler = None self.lastSeqNum = 0 self.inTxns = {} self.outTxns = {} self.sessionState = SMPPSessionStates.NONE self.encoder = PDUEncoder() self.disconnectedDeferred = defer.Deferred() # Overriden in tests #pylint: disable=no-member self.callLater = reactor.callLater self.port = None self.log = logging.getLogger(LOG_CATEGORY) def config(self): #pylint: disable=no-member return self.factory.getConfig() def connectionMade(self): """When TCP connection is made """ Protocol.connectionMade(self) self.port = self.transport.getHost().port # Start the inactivity timer the connection is dropped if we receive no data self.activateInactivityTimer() self.sessionState = SMPPSessionStates.OPEN self.log.warning("SMPP connection established from %s to port %s", self.transport.getPeer().host, self.port) def connectionLost(self, reason): Protocol.connectionLost(self, reason) self.log.warning("SMPP %s disconnected from port %s: %s", self.transport.getPeer().host, self.port, reason) self.sessionState = SMPPSessionStates.NONE self.cancelEnquireLinkTimer() self.cancelInactivityTimer() self.disconnectedDeferred.callback(None) def dataReceived(self, data): """ Looks for a full PDU (protocol data unit) and passes it from rawMessageReceived. """ if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Received data [%s]" % _safelylogOutPdu(data)) self.recvBuffer = self.recvBuffer + data while True: if self.connectionCorrupted: return msg = self.readMessage() if msg is None: break self.endPDURead() self.rawMessageReceived(msg) if len(self.recvBuffer) > 0: self.incompletePDURead() def incompletePDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): return self.pduReadTimer = self.callLater(self.config().pduReadTimerSecs, self.onPDUReadTimeout) def endPDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): self.pduReadTimer.cancel() def readMessage(self): pduLen = self.getMessageLength() if pduLen is None: return None return self.getMessage(pduLen) def getMessageLength(self): if len(self.recvBuffer) < 4: return None return struct.unpack('!L', self.recvBuffer[:4])[0] def getMessage(self, pduLen): if len(self.recvBuffer) < pduLen: return None message = self.recvBuffer[:pduLen] self.recvBuffer = self.recvBuffer[pduLen:] return message def corruptDataRecvd(self, status=CommandStatus.ESME_RINVCMDLEN): self.sendPDU(GenericNack(status=status)) self.onCorruptConnection() def onCorruptConnection(self): """ Once the connection is corrupt, the PDU boundaries are lost and it's impossible to continue processing messages. - Set a flag to indicate corrupt connection - no more parse attempts should be made for inbound data - no more outbound requests should be attempted (they should errback immediately) - Cancel outstanding outbound requests (which have not yet been ack'ed) (removed from the list and errback called) - Shutdown """ self.log.critical("Connection is corrupt!!! Shutting down...") self.connectionCorrupted = True self.cancelOutboundTransactions(SMPPClientConnectionCorruptedError()) self.shutdown() def getHeader(self, message): try: return self.encoder.decodeHeader(BytesIO(message[:self.encoder.HEADER_LEN])) except: return {} def onPDUReadTimeout(self): self.log.critical('PDU read timed out. Buffer is now considered corrupt') self.corruptDataRecvd() def rawMessageReceived(self, message): """Called once a PDU (protocol data unit) boundary is identified. Creates an SMPP PDU class from the data and calls PDUReceived dispatcher """ pdu = None try: pdu = self.encoder.decode(BytesIO(message)) except PDUCorruptError as e: self.log.exception(e) self.log.critical("Received corrupt PDU %s" % _safelylogOutPdu(message)) self.corruptDataRecvd(status=e.status) except PDUParseError as e: self.log.exception(e) self.log.critical("Received unparsable PDU %s" % _safelylogOutPdu(message)) header = self.getHeader(message) seqNum = header.get('sequence_number', None) commandId = header.get('command_id', None) self.sendPDU(getPDUClass(commandId).requireAck(seqNum=seqNum, status=e.status)) else: self.PDUReceived(pdu) def PDUReceived(self, pdu): """Dispatches incoming PDUs """ if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Received PDU: %s" % pdu) encoded = self.encoder.encode(pdu) if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Receiving data [%s]" % _safelylogOutPdu(encoded)) # Signal SMPP operation self.onSMPPOperation() if isinstance(pdu, PDURequest): self.PDURequestReceived(pdu) elif isinstance(pdu, PDUResponse): self.PDUResponseReceived(pdu) else: getattr(self, "onPDU_%s" % pdu.id.name)(pdu) def PDURequestReceived(self, reqPDU): """Handle incoming request PDUs """ if isinstance(reqPDU, PDUDataRequest): self.PDUDataRequestReceived(reqPDU) return getattr(self, "onPDURequest_%s" % reqPDU.id.name)(reqPDU) def onPDURequest_enquire_link(self, reqPDU): self.sendResponse(reqPDU) def onPDURequest_unbind(self, reqPDU): # Allow no more outbound data requests # Accept no more inbound requests self.sessionState = SMPPSessionStates.UNBIND_RECEIVED self.cancelEnquireLinkTimer() # Cancel outbound requests self.cancelOutboundTransactions(SMPPClientSessionStateError('Unbind received')) # Wait for inbound requests to finish then ack and disconnect self.finishInboundTxns().addCallback(lambda r: (self.sendResponse(reqPDU) or True) and self.disconnect()) def sendResponse(self, reqPDU, status=CommandStatus.ESME_ROK, **params): self.sendPDU(reqPDU.requireAck(reqPDU.seqNum, status, **params)) def PDUDataRequestReceived(self, reqPDU): if self.sessionState == SMPPSessionStates.UNBIND_PENDING: self.log.info("Unbind is pending...Ignoring data request PDU %s" % reqPDU) return if not self.isBound(): errMsg = 'Received data request when not bound %s' % reqPDU self.cancelOutboundTransactions(SessionStateError(errMsg, CommandStatus.ESME_RINVBNDSTS)) return self.fatalErrorOnRequest(reqPDU, errMsg, CommandStatus.ESME_RINVBNDSTS) if self.dataRequestHandler is None: return self.fatalErrorOnRequest(reqPDU, 'Missing dataRequestHandler', CommandStatus.ESME_RX_T_APPN) self.doPDURequest(reqPDU, self.dataRequestHandler) def fatalErrorOnRequest(self, reqPDU, errMsg, status): self.log.critical(errMsg) self.sendResponse(reqPDU, status) self.shutdown() def doPDURequest(self, reqPDU, handler): self.startInboundTransaction(reqPDU) handlerCall = defer.maybeDeferred(handler, self, reqPDU) handlerCall.addCallback(self.PDURequestSucceeded, reqPDU) handlerCall.addErrback(self.PDURequestFailed, reqPDU) handlerCall.addBoth(self.PDURequestFinished, reqPDU) def PDURequestSucceeded(self, dataHdlrResp, reqPDU): if reqPDU.requireAck: status = CommandStatus.ESME_ROK params = {} if dataHdlrResp: if dataHdlrResp in list(CommandStatus): status = dataHdlrResp elif isinstance(dataHdlrResp, DataHandlerResponse): status = dataHdlrResp.status params = dataHdlrResp.params else: self.log.critical("Invalid response type returned from data handler %s" % type(dataHdlrResp)) status = CommandStatus.ESME_RX_T_APPN self.shutdown() self.sendResponse(reqPDU, status, **params) def PDURequestFailed(self, error, reqPDU): if error.check(SMPPProtocolError): # Get the original error try: error.raiseException() except SMPPProtocolError as validation_error: self.log.info("Application raised error '%s', forwarding to client. Inbound PDU was [%s], hex[%s]" % ( validation_error, reqPDU, _safelylogOutPdu(self.encoder.encode(reqPDU)))) # Jasmin update: validation_error have attribute named commandStatusName # return_cmd_status = validation_error.commandStatusName return_cmd_status = validation_error.status shutdown = False else: self.log.critical('Exception raised handling inbound PDU [%s] hex[%s]: %s' % ( reqPDU, _safelylogOutPdu(self.encoder.encode(reqPDU)), error)) return_cmd_status = CommandStatus.ESME_RX_T_APPN shutdown = True if reqPDU.requireAck: self.sendResponse(reqPDU, return_cmd_status) if shutdown: self.shutdown() def PDURequestFinished(self, result, reqPDU): self.endInboundTransaction(reqPDU) return result def finishTxns(self): return defer.DeferredList([self.finishInboundTxns(), self.finishOutboundTxns()]) def finishInboundTxns(self): return defer.DeferredList(self.inTxns.values()) def finishOutboundTxns(self): return defer.DeferredList([txn.ackDeferred for txn in list(self.outTxns.values())]) def PDUResponseReceived(self, pdu): """Handle incoming response PDUs """ if isinstance(pdu, GenericNack): self.log.critical("Recevied generic_nack %s" % pdu) if pdu.seqNum is None: self.onCorruptConnection() return if pdu.seqNum not in self.outTxns: self.log.critical('Response PDU received with unknown outbound transaction sequence number %s' % pdu) return self.endOutboundTransaction(pdu) def sendPDU(self, pdu): """Send a SMPP PDU """ if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Sending PDU: %s" % pdu) encoded = self.encoder.encode(pdu) if self.log.isEnabledFor(logging.DEBUG): self.log.debug("Sending data [%s]" % _safelylogOutPdu(encoded)) self.transport.write(encoded) self.onSMPPOperation() def sendBindRequest(self, pdu): return self.sendRequest(pdu, self.config().sessionInitTimerSecs) def sendRequest(self, pdu, timeout): return defer.maybeDeferred(self.doSendRequest, pdu, timeout) def doSendRequest(self, pdu, timeout): if self.connectionCorrupted: raise SMPPClientConnectionCorruptedError() if not isinstance(pdu, PDURequest) or pdu.requireAck is None: raise SMPPClientError("Invalid PDU to send: %s" % pdu) pdu.seqNum = self.claimSeqNum() self.sendPDU(pdu) return self.startOutboundTransaction(pdu, timeout) def onSMPPOperation(self): """Called whenever an SMPP PDU is sent or received """ if self.isBound(): self.activateEnquireLinkTimer() self.activateInactivityTimer() def activateEnquireLinkTimer(self): if self.enquireLinkTimer and self.enquireLinkTimer.active(): self.enquireLinkTimer.reset(self.config().enquireLinkTimerSecs) elif self.config().enquireLinkTimerSecs: self.enquireLinkTimer = self.callLater(self.config().enquireLinkTimerSecs, self.enquireLinkTimerExpired) def activateInactivityTimer(self): if self.inactivityTimer and self.inactivityTimer.active(): self.inactivityTimer.reset(self.config().inactivityTimerSecs) elif self.config().inactivityTimerSecs: self.inactivityTimer = self.callLater(self.config().inactivityTimerSecs, self.inactivityTimerExpired) def cancelEnquireLinkTimer(self): if self.enquireLinkTimer and self.enquireLinkTimer.active(): self.enquireLinkTimer.cancel() self.enquireLinkTimer = None def cancelInactivityTimer(self): if self.inactivityTimer and self.inactivityTimer.active(): self.inactivityTimer.cancel() self.inactivityTimer = None def enquireLinkTimerExpired(self): txn = self.sendRequest(EnquireLink(), self.config().responseTimerSecs) txn.addErrback(self.enquireLinkErr) def enquireLinkErr(self, failure): # Unbinding already anyway. No need to raise another error failure.trap(SMPPError) def inactivityTimerExpired(self): self.log.critical("Inactivity timer expired...shutting down") self.shutdown() def isBound(self): return self.sessionState in ( SMPPSessionStates.BOUND_TX, SMPPSessionStates.BOUND_RX, SMPPSessionStates.BOUND_TRX) def shutdown(self): """ Unbind if appropriate and disconnect """ if self.isBound() and not self.connectionCorrupted: self.log.warning("Shutdown requested...unbinding") self.unbind().addBoth(lambda result: self.disconnect()) elif self.sessionState not in (SMPPSessionStates.UNBIND_RECEIVED, SMPPSessionStates.UNBIND_PENDING): self.log.warning("Shutdown requested...disconnecting") self.disconnect() else: self.log.debug("Shutdown already in progress") def startInboundTransaction(self, reqPDU): if reqPDU.seqNum in self.inTxns: raise SMPPProtocolError('Duplicate message id [%s] received. Already in progess.' % reqPDU.seqNum, CommandStatus.ESME_RUNKNOWNERR) txnDeferred = defer.Deferred() self.inTxns[reqPDU.seqNum] = txnDeferred self.log.debug("Inbound transaction started with message id %s" % reqPDU.seqNum) return txnDeferred def endInboundTransaction(self, reqPDU): if not reqPDU.seqNum in self.inTxns: raise ValueError('Unknown inbound sequence number in transaction for request PDU %s' % reqPDU) self.log.debug("Inbound transaction finished with message id %s" % reqPDU.seqNum) self.inTxns[reqPDU.seqNum].callback(reqPDU) del self.inTxns[reqPDU.seqNum] def startOutboundTransaction(self, reqPDU, timeout): if reqPDU.seqNum in self.outTxns: raise ValueError('Seq number [%s] is already in progess.' % reqPDU.seqNum) # Create callback deferred ackDeferred = defer.Deferred() # Create response timer timer = self.callLater(timeout, self.onResponseTimeout, reqPDU, timeout) # Save transaction self.outTxns[reqPDU.seqNum] = SMPPOutboundTxn(reqPDU, timer, ackDeferred) self.log.debug("Outbound transaction started with message id %s" % reqPDU.seqNum) return ackDeferred def closeOutboundTransaction(self, seqNum): self.log.debug("Outbound transaction finished with message id %s" % seqNum) if seqNum in self.outTxns: txn = self.outTxns[seqNum] # Remove txn del self.outTxns[seqNum] # Cancel response timer if txn.timer.active(): txn.timer.cancel() return txn else: self.log.critical('Cannot close outbound transaction: trx id [%s] not found !', seqNum) return None def endOutboundTransaction(self, respPDU): txn = self.closeOutboundTransaction(respPDU.seqNum) if txn is not None: if respPDU.status == CommandStatus.ESME_ROK: if not isinstance(respPDU, txn.request.requireAck): txn.ackDeferred.errback(SMPPProtocolError(respPDU, "Invalid PDU response type [%s] returned for request type [%s]" % ( type(respPDU), type(txn.request)))) return # Do callback txn.ackDeferred.callback(SMPPOutboundTxnResult(self, txn.request, respPDU)) return if isinstance(respPDU, GenericNack): txn.ackDeferred.errback(SMPPGenericNackTransactionError(respPDU, txn.request)) return errCode = respPDU.status txn.ackDeferred.errback(SMPPTransactionError(respPDU, txn.request)) def endOutboundTransactionErr(self, reqPDU, error): self.log.error(error) txn = self.closeOutboundTransaction(reqPDU.seqNum) if txn is not None: # Do errback txn.ackDeferred.errback(error) def cancelOutboundTransactions(self, error): for txn in list(self.outTxns.values()): self.endOutboundTransactionErr(txn.request, error) def onResponseTimeout(self, reqPDU, timeout): errMsg = 'Request timed out after %s secs: %s' % (timeout, reqPDU) self.endOutboundTransactionErr(reqPDU, SMPPRequestTimoutError(errMsg)) self.shutdown() def claimSeqNum(self): self.lastSeqNum += 1 return self.lastSeqNum def unbindSucceeded(self, result): self.sessionState = SMPPSessionStates.UNBOUND self.log.warning("Unbind succeeded") return result def unbindFailed(self, reason): self.log.error("Unbind failed [%s]. Disconnecting..." % reason) self.disconnect() if reason.check(SMPPRequestTimoutError): raise SMPPSessionInitTimoutError(str(reason)) return reason def unbindAfterInProgressTxnsFinished(self, result, unbindDeferred): self.log.warning('Issuing unbind request') self.sendBindRequest(Unbind()).addCallbacks(self.unbindSucceeded, self.unbindFailed).chainDeferred( unbindDeferred) ############################################################################ # Public command functions ############################################################################ def unbind(self): """Unbind from SMSC Result is a Deferred object """ if not self.isBound(): return defer.fail( SMPPClientSessionStateError('unbind called with illegal session state: %s' % self.sessionState)) self.cancelEnquireLinkTimer() self.log.info('Waiting for in-progress transactions to finish...') # Signal that # - no new data requests should be sent # - no new incoming data requests should be accepted self.sessionState = SMPPSessionStates.UNBIND_PENDING unbindDeferred = defer.Deferred() # Wait for any in-progress txns to finish self.finishTxns().addCallback(self.unbindAfterInProgressTxnsFinished, unbindDeferred) # Result is the deferred for the unbind txn return unbindDeferred def unbindAndDisconnect(self): """Unbind from SMSC and disconnect Result is a Deferred object """ return self.unbind().addBoth(lambda result: self.disconnect()) def disconnect(self): """Disconnect from SMSC """ if self.isBound(): self.log.warning("Disconnecting while bound to SMSC...") else: self.log.warning("Disconnecting...") self.sessionState = SMPPSessionStates.UNBOUND self.transport.loseConnection() def getDisconnectedDeferred(self): """Get a Deferred so you can be notified on disconnect """ return self.disconnectedDeferred def sendDataRequest(self, pdu): """Send a SMPP Request Message Argument is an SMPP PDUDataRequest (protocol data unit). Result is a Deferred object """ if not isinstance(pdu, PDUDataRequest): return defer.fail(SMPPClientError("Invalid PDU passed to sendDataRequest(): %s" % pdu)) if not self.isBound(): return defer.fail(SMPPClientSessionStateError('Not bound')) return self.sendRequest(pdu, self.config().responseTimerSecs)
def test_decodeHeader_command_length_too_short(self): self.do_decode_corrupt_data_error_test( PDUEncoder().decodeHeader, CommandStatus.ESME_RINVCMDLEN, b'0000000f000000060000000000000000')
def test_decode_bad_message_length_msg_too_long(self): self.do_decode_corrupt_data_error_test( PDUEncoder().decode, CommandStatus.ESME_RINVCMDLEN, b'0000001c80000009000000000000000154534937353838000210000134')
def test_BindTransceiverResp_error_has_no_body(self): pdu = BindTransceiverResp(3456, status=CommandStatus.ESME_RINVPASWD) self.assertEqual(0, len(pdu.params)) self.do_conversion_test(PDUEncoder(), pdu, b'00000010800000090000000e00000d80')
def getPDU(self, hexStr): return PDUEncoder().decode(BytesIO(binascii.a2b_hex(hexStr)))
def _send_PDU(self, pdu: PDU): self._transport.write(PDUEncoder().encode(pdu))