From 8d7bd3427944603bd33b117c1ba6b880f7a694db Mon Sep 17 00:00:00 2001 From: Dom Amato Date: Fri, 10 Apr 2020 19:36:33 -0500 Subject: [PATCH] Python3 (#1) --- .github/workflows/pythontest.yml | 43 + .travis.yml | 11 - README.markdown => README.md | 7 +- dev_requirements.txt | 1 - .../twisted/examples => examples}/__init__.py | 0 .../examples => examples}/transceiver.py | 2 +- requirements.txt | 7 +- setup.py | 25 +- smpp/__init__.py | 2 +- smpp/twisted/client.py | 49 +- smpp/twisted/config.py | 17 +- smpp/twisted/protocol.py | 530 +++++---- smpp/twisted/server.py | 66 +- smpp/twisted/tests/__init__.py | 15 - tests/__init__.py | 3 + .../twisted/tests => tests}/smsc_simulator.py | 206 ++-- .../test_smpp_client.py | 254 +++-- .../test_smpp_protocol.py | 104 +- .../tests => tests}/test_smpp_server.py | 1014 +++++++++-------- 19 files changed, 1249 insertions(+), 1107 deletions(-) create mode 100644 .github/workflows/pythontest.yml delete mode 100644 .travis.yml rename README.markdown => README.md (80%) delete mode 100644 dev_requirements.txt rename {smpp/twisted/examples => examples}/__init__.py (100%) rename {smpp/twisted/examples => examples}/transceiver.py (98%) delete mode 100644 smpp/twisted/tests/__init__.py create mode 100644 tests/__init__.py rename {smpp/twisted/tests => tests}/smsc_simulator.py (81%) rename smpp/twisted/tests/smpp_client_test.py => tests/test_smpp_client.py (90%) rename smpp/twisted/tests/smpp_protocol_test.py => tests/test_smpp_protocol.py (92%) rename {smpp/twisted/tests => tests}/test_smpp_server.py (93%) diff --git a/.github/workflows/pythontest.yml b/.github/workflows/pythontest.yml new file mode 100644 index 0000000..55af722 --- /dev/null +++ b/.github/workflows/pythontest.yml @@ -0,0 +1,43 @@ +# This workflow will install Python dependencies, run tests and lint with a variety of Python versions +# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions + +name: Python Test + +on: + push: + branches: + - '**' + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.5, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e git+https://github.com/DomAmato/smpp.pdu.git@master#egg=smpp.pdu + pip install -r requirements.txt + pip install service_identity + - name: Lint + run: | + pip install pylint + pylint -rn --errors-only ./smpp + - name: Test + env: + COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} + run: | + pip install coveralls mock + coverage run --source=smpp -m twisted.trial tests + coveralls diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index be501d8..0000000 --- a/.travis.yml +++ /dev/null @@ -1,11 +0,0 @@ -language: python - -python: - - "2.6" - - "2.7" - -install: - - "pip install -r requirements.txt --use-mirrors" - - "pip install -r dev_requirements.txt --use-mirrors" - -script: py.test diff --git a/README.markdown b/README.md similarity index 80% rename from README.markdown rename to README.md index 101064d..a11e72c 100644 --- a/README.markdown +++ b/README.md @@ -1,3 +1,8 @@ +# smpp.twisted + +[![Test](https://github.com/DomAmato/smpp.twisted/workflows/Python%20Test/badge.svg)](https://github.com/DomAmato/smpp.twisted/actions) +[![Coverage Status](https://coveralls.io/repos/github/DomAmato/smpp.twisted/badge.svg?branch=master)](https://coveralls.io/github/DomAmato/smpp.twisted?branch=master) + SMPP 3.4 client built on Twisted http://www.nowsms.com/discus/messages/1/24856.html @@ -9,7 +14,7 @@ Example from smpp.twisted.client import SMPPClientTransceiver, SMPPClientService from smpp.twisted.config import SMPPClientConfig - class SMPP(object): + class SMPP: def __init__(self, config=None): if config is None: diff --git a/dev_requirements.txt b/dev_requirements.txt deleted file mode 100644 index 932a895..0000000 --- a/dev_requirements.txt +++ /dev/null @@ -1 +0,0 @@ -mock diff --git a/smpp/twisted/examples/__init__.py b/examples/__init__.py similarity index 100% rename from smpp/twisted/examples/__init__.py rename to examples/__init__.py diff --git a/smpp/twisted/examples/transceiver.py b/examples/transceiver.py similarity index 98% rename from smpp/twisted/examples/transceiver.py rename to examples/transceiver.py index ea8a9ae..79516d2 100644 --- a/smpp/twisted/examples/transceiver.py +++ b/examples/transceiver.py @@ -3,7 +3,7 @@ from smpp.twisted.client import SMPPClientTransceiver, SMPPClientService from smpp.twisted.config import SMPPClientConfig -class SMPP(object): +class SMPP: def __init__(self, config=None): if config is None: diff --git a/requirements.txt b/requirements.txt index 6fb5eed..243d987 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ -twisted -enum -pyOpenSSL +Twisted~=20.3.0 +pyOpenSSL~=19.1.0 +# cant install from other projects +# smpp.pdu @ git+https://github.com/DomAmato/smpp.pdu.git@master smpp.pdu diff --git a/setup.py b/setup.py index 23fcfa1..0e5e343 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,14 @@ import os from setuptools import setup, find_packages +def parse_requirements(filename): + """load requirements from a pip requirements file""" + lineiter = (line.strip() for line in open(filename)) + return [line for line in lineiter if line and (not line.startswith("#") and not line.startswith('-'))] + def read(fname): return open(os.path.join(os.path.dirname(__file__), fname)).read() -from setuptools import setup, find_packages setup( name = "smpp.twisted", version = "0.4", @@ -12,24 +16,18 @@ def read(fname): author_email = "roger.hoover@gmail.com", description = "SMPP 3.4 client built on Twisted", license = 'Apache License 2.0', - packages = find_packages(), - long_description=read('README.markdown'), + packages = find_packages(exclude=["tests"]), + long_description=read('README.md'), keywords = "smpp twisted", url = "https://github.com/mozes/smpp.twisted", py_modules=["smpp.twisted"], include_package_data = True, - package_data={'smpp.twisted': ['README.markdown']}, + package_data={'smpp.twisted': ['README.md']}, zip_safe = False, - install_requires = [ - 'twisted', - 'enum', - 'pyOpenSSL', - 'smpp.pdu', - ], + install_requires = parse_requirements('requirements.txt'), tests_require = [ 'mock', ], - test_suite = 'smpp.twisted.tests', classifiers=[ "Development Status :: 5 - Production/Stable", "Framework :: Twisted", @@ -38,6 +36,11 @@ def read(fname): "License :: OSI Approved :: Apache Software License", "Intended Audience :: Developers", "Programming Language :: Python", + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', "Topic :: Software Development :: Libraries :: Python Modules", ], ) diff --git a/smpp/__init__.py b/smpp/__init__.py index 96f38be..750fafb 100644 --- a/smpp/__init__.py +++ b/smpp/__init__.py @@ -13,4 +13,4 @@ See the License for the specific language governing permissions and limitations under the License. """ -__import__('pkg_resources').declare_namespace(__name__) \ No newline at end of file +__path__ = __import__('pkgutil').extend_path(__path__, __name__) \ No newline at end of file diff --git a/smpp/twisted/client.py b/smpp/twisted/client.py index 8d7dc7b..f7d1bbe 100644 --- a/smpp/twisted/client.py +++ b/smpp/twisted/client.py @@ -17,7 +17,7 @@ from OpenSSL import SSL from twisted.internet.protocol import ClientFactory from twisted.internet import defer, reactor, ssl -from twisted.application import service +from twisted.application.service import Service from smpp.twisted.protocol import SMPPClientProtocol, DataHandlerResponse LOG_CATEGORY="smpp.twisted.client" @@ -30,16 +30,17 @@ def __init__(self, config): self.config = config self.buildProtocolDeferred = defer.Deferred() self.log = logging.getLogger(LOG_CATEGORY) - + def getConfig(self): return self.config def buildProtocol(self, addr): p = ClientFactory.buildProtocol(self, addr) #This is a sneaky way of passing the protocol instance back to the caller + #pylint: disable=no-member reactor.callLater(0, self.buildProtocolDeferred.callback, p) return p - + def clientConnectionFailed(self, connector, reason): """Connection failed """ @@ -50,7 +51,7 @@ class CtxFactory(ssl.ClientContextFactory): def __init__(self, config): self.smppConfig = config - + def getContext(self): self.method = SSL.SSLv23_METHOD ctx = ssl.ClientContextFactory.getContext(self) @@ -58,9 +59,9 @@ def getContext(self): ctx.use_certificate_file(self.smppConfig.SSLCertificateFile) return ctx -class SMPPClientBase(object): +class SMPPClientBase: msgHandler = None - + def __init__(self, config): self.config = config self.log = logging.getLogger(LOG_CATEGORY) @@ -71,44 +72,46 @@ def connect(self): factory = SMPPClientFactory(self.config) if self.config.useSSL: self.log.warning('Establishing SSL connection to %s:%d' % (self.config.host, self.config.port)) + #pylint: disable=no-member reactor.connectSSL(self.config.host, self.config.port, factory, CtxFactory(self.config)) else: self.log.warning('Establishing TCP connection to %s:%d' % (self.config.host, self.config.port)) - reactor.connectTCP(self.config.host, self.config.port, factory) + #pylint: disable=no-member + reactor.connectTCP(self.config.host, self.config.port, factory) return factory.buildProtocolDeferred.addCallback(self.onConnect) - + def onConnect(self, smpp): self.smpp = smpp if self.msgHandler is not None: smpp.setDataRequestHandler(self.msgHandler) return smpp - + def connectAndBind(self): self.bindDeferred = defer.Deferred() self.connect().addCallback(self.doBind).addErrback(self.bindDeferred.errback) return self.bindDeferred - + def doBind(self, smpp): self.bind(smpp).addCallback(self.bound).addErrback(self.bindFailed, smpp) return smpp - + def bind(self, smpp): raise NotImplementedError() - + #If bind fails, don't errback until we're disconnected def bindFailed(self, error, smpp): smpp.getDisconnectedDeferred().addCallback(lambda result: self.bindDeferred.errback(error)) - + def bound(self, result): self.bindDeferred.callback(result.smpp) class SMPPClientTransmitter(SMPPClientBase): - + def bind(self, smpp): return smpp.bindAsTransmitter() class SMPPClientReceiver(SMPPClientBase): - + def __init__(self, config, msgHandler): SMPPClientBase.__init__(self, config) self.msgHandler = msgHandler @@ -123,31 +126,31 @@ def bind(self, smpp): #TODO - move this to mozes code base since # the service support in Twisted is so crappy -class SMPPClientService(service.Service): - +class SMPPClientService(Service): + def __init__(self, smppClient): self.client = smppClient self.stopDeferred = defer.Deferred() self.log = logging.getLogger(LOG_CATEGORY) - + def getStopDeferred(self): return self.stopDeferred - + @defer.inlineCallbacks def startService(self): - service.Service.startService(self) + Service.startService(self) bindDeferred = self.client.connectAndBind() bindDeferred.addErrback(self.handleStartError) smpp = yield bindDeferred smpp.getDisconnectedDeferred().chainDeferred(self.stopDeferred) defer.returnValue(smpp) - + def handleStartError(self, error): self.stopDeferred.errback(error) return error - + def stopService(self): - service.Service.stopService(self) + Service.stopService(self) if self.client.smpp: self.log.info("Stopping SMPP Client") return self.client.smpp.unbindAndDisconnect() diff --git a/smpp/twisted/config.py b/smpp/twisted/config.py index 20bdcd8..6d181f4 100644 --- a/smpp/twisted/config.py +++ b/smpp/twisted/config.py @@ -13,8 +13,9 @@ See the License for the specific language governing permissions and limitations under the License. """ -class SMPPConfig(object): - + +class SMPPConfig: + def __init__(self, **kwargs): self.sessionInitTimerSecs = kwargs.get('sessionInitTimerSecs', 30) self.enquireLinkTimerSecs = kwargs.get('enquireLinkTimerSecs', 10) @@ -22,13 +23,12 @@ def __init__(self, **kwargs): self.responseTimerSecs = kwargs.get('responseTimerSecs', 60) self.pduReadTimerSecs = kwargs.get('pduReadTimerSecs', 10) - class SMPPClientConfig(SMPPConfig): - + def __init__(self, **kwargs): super(SMPPClientConfig, self).__init__(**kwargs) - self.port = kwargs['port'] self.host = kwargs['host'] + self.port = kwargs['port'] self.username = kwargs['username'] self.password = kwargs['password'] self.systemType = kwargs.get('systemType', '') @@ -36,10 +36,10 @@ def __init__(self, **kwargs): self.SSLCertificateFile = kwargs.get('SSLCertificateFile', None) self.addressRange = kwargs.get('addressRange', None) self.addressTon = kwargs.get('addressTon', None) - self.addressNpi = kwargs.get('addressNpi', None) + self.addressNpi = kwargs.get('addressNpi', None) class SMPPServerConfig(SMPPConfig): - + def __init__(self, **kwargs): """ @param systems: A dict of data representing the available @@ -50,5 +50,4 @@ def __init__(self, **kwargs): """ super(SMPPServerConfig, self).__init__(**kwargs) self.systems = kwargs.get('systems', {}) - self.msgHandler = kwargs['msgHandler'] - + self.msgHandler = kwargs['msgHandler'] \ No newline at end of file diff --git a/smpp/twisted/protocol.py b/smpp/twisted/protocol.py index c4e4d09..dbab1e8 100644 --- a/smpp/twisted/protocol.py +++ b/smpp/twisted/protocol.py @@ -1,6 +1,6 @@ """ Copyright 2009-2010 Mozes, Inc. - + Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -14,60 +14,73 @@ limitations under the License. """ -import struct, logging, StringIO, binascii +from io import BytesIO +import struct +import logging +import binascii from enum import Enum -from smpp.pdu.namedtuple import namedtuple -from smpp.pdu.operations import * +from collections import namedtuple +from smpp.pdu.operations import ( + GenericNack, + getPDUClass, + EnquireLink, + BindTransmitter, + BindTransceiver, + BindReceiver, + Unbind +) from smpp.pdu.pdu_encoding import PDUEncoder from smpp.pdu.pdu_types import PDURequest, PDUResponse, PDUDataRequest, CommandStatus -from smpp.pdu.error import * +from smpp.pdu.error import ( + SMPPClientConnectionCorruptedError, + PDUCorruptError, + PDUParseError, + SMPPClientSessionStateError, + SessionStateError, + SMPPProtocolError, + SMPPClientError, + SMPPError, + SMPPGenericNackTransactionError, + SMPPTransactionError, + SMPPRequestTimoutError, + SMPPSessionInitTimoutError +) from smpp.pdu.constants import command_status_name_map -from twisted.internet import protocol, defer, reactor +from twisted.internet.protocol import Protocol +from twisted.internet import defer, reactor from twisted.internet.defer import inlineCallbacks from twisted.cred import error -import exceptions - - -LOG_CATEGORY="smpp.twisted.protocol" - -SMPPSessionStates = Enum( - 'NONE', - 'OPEN', - 'BIND_TX_PENDING', - 'BOUND_TX', - 'BIND_RX_PENDING', - 'BOUND_RX', - 'BIND_TRX_PENDING', - 'BOUND_TRX', - 'UNBIND_PENDING', - 'UNBIND_RECEIVED', - 'UNBOUND' -) + +LOG_CATEGORY = "smpp.twisted.protocol" + +SMPPSessionStates = Enum('SMPPSessionStates', 'NONE, OPEN, BIND_TX_PENDING, BOUND_TX, BIND_RX_PENDING, BOUND_RX, BIND_TRX_PENDING, BOUND_TRX, UNBIND_PENDING, UNBIND_RECEIVED, UNBOUND') SMPPOutboundTxn = namedtuple('SMPPOutboundTxn', 'request, timer, ackDeferred') SMPPOutboundTxnResult = namedtuple('SMPPOutboundTxnResult', 'smpp, request, response') + def _safelylogOutPdu(content): try: return binascii.b2a_hex(content) - except exceptions.UnicodeEncodeError: + except UnicodeEncodeError: return "Couldn't log out the pdu content due to non-ascii characters." -class DataHandlerResponse(object): - +class DataHandlerResponse: + def __init__(self, status, **params): self.status = status self.params = params -class SMPPProtocolBase( protocol.Protocol ): + +class SMPPProtocolBase(Protocol): """Short Message Peer to Peer Protocol v3.4 implementing ESME (client)""" version = 0x34 - def __init__( self ): - self.recvBuffer = "" + def __init__(self): + self.recvBuffer = b"" self.connectionCorrupted = False self.pduReadTimer = None self.enquireLinkTimer = None @@ -80,42 +93,45 @@ def __init__( self ): 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.Protocol.connectionMade(self) + Protocol.connectionMade(self) self.port = self.transport.getHost().port - #Start the inactivity timer the connection is dropped if we receive no data + # 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 ) + 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 ): + 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)) - + 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 @@ -124,10 +140,10 @@ def dataReceived( self, data ): break self.endPDURead() self.rawMessageReceived(msg) - + if len(self.recvBuffer) > 0: self.incompletePDURead() - + def incompletePDURead(self): if self.pduReadTimer and self.pduReadTimer.active(): return @@ -147,19 +163,19 @@ 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 - + 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. @@ -176,28 +192,28 @@ def onCorruptConnection(self): self.shutdown() def getHeader(self, message): - try: - return self.encoder.decodeHeader(StringIO.StringIO(message[:self.encoder.HEADER_LEN])) + 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 ): + 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: + 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, e: + except PDUParseError as e: self.log.exception(e) self.log.critical("Received unparsable PDU %s" % _safelylogOutPdu(message)) header = self.getHeader(message) @@ -207,26 +223,26 @@ def rawMessageReceived( self, message ): else: self.PDUReceived(pdu) - def PDUReceived( self, 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 + + # 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) + getattr(self, "onPDU_%s" % pdu.id._name_)(pdu) def PDURequestReceived(self, reqPDU): """Handle incoming request PDUs @@ -234,59 +250,59 @@ def PDURequestReceived(self, reqPDU): if isinstance(reqPDU, PDUDataRequest): self.PDUDataRequestReceived(reqPDU) return - - getattr(self, "onPDURequest_%s" % str(reqPDU.id))(reqPDU) - + + 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 + # Allow no more outbound data requests + # Accept no more inbound requests self.sessionState = SMPPSessionStates.UNBIND_RECEIVED self.cancelEnquireLinkTimer() - #Cancel outbound requests + # Cancel outbound requests self.cancelOutboundTransactions(SMPPClientSessionStateError('Unbind received')) - #Wait for inbound requests to finish then ack and disconnect + # 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: + if dataHdlrResp in list(CommandStatus): status = dataHdlrResp elif isinstance(dataHdlrResp, DataHandlerResponse): status = dataHdlrResp.status @@ -295,42 +311,46 @@ def PDURequestSucceeded(self, dataHdlrResp, reqPDU): 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 + 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)) + 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 - + 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()]) - + return defer.DeferredList([txn.ackDeferred for txn in list(self.outTxns.values())]) + def PDUResponseReceived(self, pdu): """Handle incoming response PDUs """ @@ -339,11 +359,11 @@ def PDUResponseReceived(self, 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): @@ -352,29 +372,29 @@ def sendPDU(self, 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.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: + 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 """ @@ -388,13 +408,13 @@ def activateEnquireLinkTimer(self): self.enquireLinkTimer.reset(self.config().enquireLinkTimerSecs) elif self.config().enquireLinkTimerSecs: self.enquireLinkTimer = self.callLater(self.config().enquireLinkTimerSecs, self.enquireLinkTimerExpired) - - def activateInactivityTimer(self): + + 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() @@ -408,7 +428,7 @@ def cancelInactivityTimer(self): 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) @@ -416,10 +436,11 @@ def enquireLinkErr(self, failure): 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) - + return self.sessionState in ( + SMPPSessionStates.BOUND_TX, SMPPSessionStates.BOUND_RX, SMPPSessionStates.BOUND_TRX) + def shutdown(self): """ Unbind if appropriate and disconnect """ @@ -431,133 +452,145 @@ def shutdown(self): 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) + 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 + + # Create callback deferred ackDeferred = defer.Deferred() - #Create response timer + # Create response timer timer = self.callLater(timeout, self.onResponseTimeout, reqPDU, timeout) - #Save transaction + # 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 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 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)))) + + 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 - #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)) - + + 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) + + if txn is not None: + # Do errback + txn.ackDeferred.errback(error) def cancelOutboundTransactions(self, error): - for txn in self.outTxns.values(): + 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) - + 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)) + 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 + + # 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 + # Wait for any in-progress txns to finish self.finishTxns().addCallback(self.unbindAfterInProgressTxnsFinished, unbindDeferred) - #Result is the deferred for the unbind txn + # 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 """ @@ -567,127 +600,129 @@ def disconnect(self): 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 ): + + 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 ): + 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) - - + + class SMPPClientProtocol(SMPPProtocolBase): - + def __init__(self): - self.log = logging.getLogger(LOG_CATEGORY) + self.log = logging.getLogger(LOG_CATEGORY) SMPPProtocolBase.__init__(self) - + self.alertNotificationHandler = None - - def PDUReceived( self, pdu ): + + def PDUReceived(self, pdu): """Dispatches incoming PDUs """ - self.log.info("SMPP Client received PDU [command: %s, sequence_number: %s, command_status: %s]" % (pdu.id, pdu.seqNum, pdu.status)) + self.log.info("SMPP Client received PDU [command: %s, sequence_number: %s, command_status: %s]" % ( + pdu.id, pdu.seqNum, pdu.status)) SMPPProtocolBase.PDUReceived(self, pdu) - + def bind(self, pdu, pendingState, boundState): if self.sessionState != SMPPSessionStates.OPEN: - return defer.fail(SMPPClientSessionStateError('bind called with illegal session state: %s' % self.sessionState)) - + return defer.fail( + SMPPClientSessionStateError('bind called with illegal session state: %s' % self.sessionState)) + bindDeferred = self.sendBindRequest(pdu) bindDeferred.addCallback(self.bindSucceeded, boundState) bindDeferred.addErrback(self.bindFailed) self.sessionState = pendingState return bindDeferred - + def doBindAsReceiver(self): self.log.warning('Requesting bind as receiver') pdu = BindReceiver( - system_id = self.config().username, - password = self.config().password, - system_type = self.config().systemType, - address_range = self.config().addressRange, - addr_ton = self.config().addressTon, - addr_npi = self.config().addressNpi, - interface_version = self.version + system_id=self.config().username, + password=self.config().password, + system_type=self.config().systemType, + address_range=self.config().addressRange, + addr_ton=self.config().addressTon, + addr_npi=self.config().addressNpi, + interface_version=self.version ) return self.bind(pdu, SMPPSessionStates.BIND_RX_PENDING, SMPPSessionStates.BOUND_RX) - + def bindSucceeded(self, result, nextState): self.sessionState = nextState self.log.warning("Bind succeeded...now in state %s" % str(self.sessionState)) self.activateEnquireLinkTimer() return result - + def bindFailed(self, reason): self.log.error("Bind failed [%s]. Disconnecting..." % reason) self.disconnect() if reason.check(SMPPRequestTimoutError): raise SMPPSessionInitTimoutError(str(reason)) return reason - + def onPDU_outbind(self, pdu): if self.sessionState != SMPPSessionStates.OPEN: self.log.critical('Received outbind command in invalid state %s' % str(self.sessionState)) self.shutdown() return - + self.log.warning("Received outbind command") self.doBindAsReceiver() - + def onPDU_alert_notification(self, pdu): if self.sessionState == SMPPSessionStates.UNBIND_PENDING: self.log.info("Unbind is pending...Ignoring alert notification PDU %s" % pdu) return - + if not self.isBound(): errMsg = 'Received alert notification when not bound %s' % pdu self.cancelOutboundTransactions(SessionStateError(errMsg, CommandStatus.ESME_RINVBNDSTS)) self.log.critical(errMsg) self.shutdown() return - + if self.alertNotificationHandler: try: self.alertNotificationHandler(self, pdu) - except Exception, e: + except Exception as e: self.log.critical('Alert handler threw exception: %s' % str(e)) self.log.exception(e) self.shutdown() - + ############################################################################ # Public command functions ############################################################################ def bindAsTransmitter(self): """Bind to SMSC as transmitter - + Result is a Deferred object """ self.log.warning('Requesting bind as transmitter') pdu = BindTransmitter( - system_id = self.config().username, - password = self.config().password, - system_type = self.config().systemType, - address_range = self.config().addressRange, - addr_ton = self.config().addressTon, - addr_npi = self.config().addressNpi, - interface_version = self.version + system_id=self.config().username, + password=self.config().password, + system_type=self.config().systemType, + address_range=self.config().addressRange, + addr_ton=self.config().addressTon, + addr_npi=self.config().addressNpi, + interface_version=self.version ) return self.bind(pdu, SMPPSessionStates.BIND_TX_PENDING, SMPPSessionStates.BOUND_TX) def bindAsReceiver(self, dataRequestHandler): """Bind to SMSC as receiver - + Result is a Deferred object """ self.setDataRequestHandler(dataRequestHandler) @@ -695,27 +730,27 @@ def bindAsReceiver(self, dataRequestHandler): def bindAsTransceiver(self, dataRequestHandler): """Bind to SMSC as transceiver - + Result is a Deferred object """ self.setDataRequestHandler(dataRequestHandler) self.log.warning('Requesting bind as transceiver') pdu = BindTransceiver( - system_id = self.config().username, - password = self.config().password, - system_type = self.config().systemType, - address_range = self.config().addressRange, - addr_ton = self.config().addressTon, - addr_npi = self.config().addressNpi, - interface_version = self.version + system_id=self.config().username, + password=self.config().password, + system_type=self.config().systemType, + address_range=self.config().addressRange, + addr_ton=self.config().addressTon, + addr_npi=self.config().addressNpi, + interface_version=self.version ) return self.bind(pdu, SMPPSessionStates.BIND_TRX_PENDING, SMPPSessionStates.BOUND_TRX) - + def setDataRequestHandler(self, handler): """Set handler to use for receiving data requests """ self.dataRequestHandler = handler - + def setAlertNotificationHandler(self, handler): """Set handler to use for receiving data requests """ @@ -723,28 +758,34 @@ def setAlertNotificationHandler(self, handler): class SMPPServerProtocol(SMPPProtocolBase): - + def __init__(self): SMPPProtocolBase.__init__(self) + + # Jasmin update: dataRequestHandler is set from factory instead of config() # Divert received messages to the handler defined in the config self.dataRequestHandler = lambda *args, **kwargs: self.config().msgHandler(self.system_id, *args, **kwargs) + self.system_id = None self.log = logging.getLogger(LOG_CATEGORY) - + def onResponseTimeout(self, reqPDU, timeout): - errMsg = 'Request timed out for system id %s after %s secs: %s' % (self.system_id, timeout, reqPDU) + errMsg = 'Request timed out for system id %s after %s secs: %s' % (self.system_id, timeout, reqPDU) self.endOutboundTransactionErr(reqPDU, SMPPRequestTimoutError(errMsg)) self.shutdown() - + def connectionLost(self, reason): # Remove this connection from those stored in the factory + #pylint: disable=no-member self.factory.removeConnection(self) SMPPProtocolBase.connectionLost(self, reason) - - def PDUReceived( self, pdu ): + + def PDUReceived(self, pdu): """Dispatches incoming PDUs """ - self.log.debug("SMPP Server received PDU to system '%s' [command: %s, sequence_number: %s, command_status: %s]" % (self.system_id, pdu.id, pdu.seqNum, pdu.status)) + self.log.debug( + "SMPP Server received PDU to system '%s' [command: %s, sequence_number: %s, command_status: %s]" % ( + self.system_id, pdu.id, pdu.seqNum, pdu.status)) SMPPProtocolBase.PDUReceived(self, pdu) def onPDURequest_enquire_link(self, reqPDU): @@ -752,60 +793,63 @@ def onPDURequest_enquire_link(self, reqPDU): self.sendResponse(reqPDU) else: self.sendResponse(reqPDU, status=CommandStatus.ESME_RINVBNDSTS) - + def onPDURequest_bind_receiver(self, reqPDU): self.doBindRequest(reqPDU, SMPPSessionStates.BOUND_RX) - + def onPDURequest_bind_transmitter(self, reqPDU): self.doBindRequest(reqPDU, SMPPSessionStates.BOUND_TX) - + def onPDURequest_bind_transceiver(self, reqPDU): self.doBindRequest(reqPDU, SMPPSessionStates.BOUND_TRX) - + @inlineCallbacks def doBindRequest(self, reqPDU, sessionState): # Check the authentication - system_id, password = reqPDU.params['system_id'], reqPDU.params['password'] + # Decode from byte strings to strings + system_id, password = reqPDU.params['system_id'].decode(), reqPDU.params['password'].decode() # Authenticate system_id and password try: + #pylint: disable=no-member iface, auth_avatar, logout = yield self.factory.login(system_id, password, self.transport.getPeer().host) except error.UnauthorizedLogin: - self.log.warning('SMPP Bind request failed for system_id: "%s", failed to authenticate' % system_id) - self.sendErrorResponse(reqPDU, CommandStatus.ESME_RINVPASWD, system_id) + #pylint: disable=no-member + if system_id not in list(self.factory.config.systems): + self.log.warning('SMPP Bind request failed for system_id: "%s", System ID not configured' % system_id) + self.sendErrorResponse(reqPDU, CommandStatus.ESME_RINVSYSID, system_id) + else: + self.log.warning('SMPP Bind request failed for system_id: "%s", failed to authenticate' % system_id) + self.sendErrorResponse(reqPDU, CommandStatus.ESME_RINVPASWD, system_id) return - - # Only a configured system_id can bind - if system_id not in self.factory.config.systems.keys(): - self.log.warning('SMPP Bind request failed for system_id: "%s", System ID not configured' % system_id) - self.sendErrorResponse(reqPDU, CommandStatus.ESME_RINVSYSID, system_id) - return # Check we're not already bound, and are open to being bound if self.sessionState != SMPPSessionStates.OPEN: self.log.warning('Duplicate SMPP bind request received from: %s' % system_id) self.sendErrorResponse(reqPDU, CommandStatus.ESME_RALYBND, system_id) return - + # Check that system_id hasn't exceeded number of allowed binds bind_type = reqPDU.commandId + #pylint: disable=no-member if not self.factory.canOpenNewConnection(system_id, bind_type): self.log.warning('SMPP System %s has exceeded maximum number of %s bindings' % (system_id, bind_type)) self.sendErrorResponse(reqPDU, CommandStatus.ESME_RBINDFAIL, system_id) return - + # If we get to here, bind successfully self.system_id = system_id self.sessionState = sessionState self.bind_type = bind_type - + + #pylint: disable=no-member self.factory.addBoundConnection(self) bound_cnxns = self.factory.getBoundConnections(system_id) - self.log.info('Bind request succeeded for %s. %d active binds' % (system_id, bound_cnxns.getBindingCount() if bound_cnxns else 0)) + self.log.info('Bind request succeeded for %s. %d active binds' % ( + system_id, bound_cnxns.getBindingCount() if bound_cnxns else 0)) self.sendResponse(reqPDU, system_id=system_id) - + def sendErrorResponse(self, reqPDU, status, system_id): """ Send an error response to reqPDU, with the specified command status.""" err_pdu = reqPDU.requireAck(seqNum=reqPDU.seqNum, status=status, system_id=system_id) self.sendPDU(err_pdu) - diff --git a/smpp/twisted/server.py b/smpp/twisted/server.py index dc6fb8b..b2bc2af 100644 --- a/smpp/twisted/server.py +++ b/smpp/twisted/server.py @@ -4,7 +4,8 @@ from zope.interface import Interface from twisted.internet.protocol import ServerFactory -from twisted import cred +# Jasmin update: direct import of UsernamePassword instead of cred +from twisted.cred.credentials import UsernamePassword from twisted.cred import error from twisted.internet import defer @@ -14,15 +15,17 @@ LOG_CATEGORY="smpp.twisted.server" +#pylint: disable=inherit-non-class class IAuthenticatedSMPP(Interface): pass -class UsernameAndPasswordAndIP(cred.credentials.UsernamePassword): +# Jasmin update: using UsernamePassword instead of cred.credentials.UsernamePassword +class UsernameAndPasswordAndIP(UsernamePassword): def __init__(self, username, password, client_ip_address): self.username = username self.password = password self.client_ip_address = client_ip_address - + class SMPPServerFactory(ServerFactory): protocol = SMPPServerProtocol @@ -31,24 +34,24 @@ def __init__(self, config, auth_portal): self.config = config self.log = logging.getLogger(LOG_CATEGORY) # A dict of protocol instances for each of the current connections, - # indexed by system_id + # indexed by system_id self.bound_connections = {} self._auth_portal = auth_portal - + def getConfig(self): return self.config - + def getBoundConnectionCount(self, system_id): - if self.bound_connections.has_key(system_id): + if system_id in self.bound_connections: return self.bound_connections[system_id].getMaxTransmitReceiveBindCount() else: return 0 def getBoundConnectionCountsStr(self, system_id): - if self.bound_connections.has_key(system_id): + if system_id in self.bound_connections: bind_counts = self.bound_connections[system_id].getBindingCountByType() bound_connections_count = [] - for key, value in bind_counts.iteritems(): + for key, value in bind_counts.items(): bound_connections_count.append("%s: %d" % (key, value)) bound_connections_str = ', '.join(bound_connections_count) return bound_connections_str @@ -67,7 +70,7 @@ def addBoundConnection(self, connection): self.bound_connections[system_id].addBinding(connection) bind_type = connection.bind_type self.log.info("Added %s bind for '%s'. Active binds: %s. Max binds: %s" % (bind_type, system_id, self.getBoundConnectionCountsStr(system_id), self.config.systems[system_id]['max_bindings'])) - + def removeConnection(self, connection): """ Remove a protocol instance (SMPP binding) from the list of current connections. @@ -83,10 +86,10 @@ def removeConnection(self, connection): # If this is the last binding for this service then remove the BindManager if self.bound_connections[system_id].getBindingCount() == 0: self.bound_connections.pop(system_id) - + def getBoundConnections(self, system_id): return self.bound_connections.get(system_id) - + def login(self, system_id, password, client_ip_address): if self._auth_portal is not None: return self._auth_portal.login( @@ -95,7 +98,7 @@ def login(self, system_id, password, client_ip_address): IAuthenticatedSMPP ) raise error.UnauthorizedLogin() - + def canOpenNewConnection(self, system_id, bind_type): """ Checks if the gateway with the specified system_id can open a new @@ -109,7 +112,7 @@ def canOpenNewConnection(self, system_id, bind_type): else: # No existing bindings for this system_id return self.config.systems[system_id]['max_bindings'] > 0 - + def unbindGateway(self, system_id): """ Unbinds and disconnects all the bindings for the given system_id. """ bind_mgr = self.getBoundConnections(system_id) @@ -121,7 +124,7 @@ def unbindGateway(self, system_id): d = defer.DeferredList(unbinds_list) else: d = defer.succeed(None) - + return d def unbindAndRemoveGateway(self, system_id): @@ -138,8 +141,8 @@ def removeGatewayFromConfig(self, deferred_res, system_id): self.config.systems.pop(system_id) return deferred_res -class SMPPBindManager(object): - +class SMPPBindManager: + def __init__(self, system_id): self.system_id = system_id self._binds = {pdu_types.CommandId.bind_transceiver: [], @@ -147,40 +150,40 @@ def __init__(self, system_id): pdu_types.CommandId.bind_receiver: []} # A queue of the most recent bindings used for delivering messages self._delivery_binding_history = collections.deque() - + def addBinding(self, connection): """ @param connection: An instance of SMPPServerProtocol """ - + bind_type = connection.bind_type self._binds[bind_type].append(connection) - + def removeBinding(self, connection): """ @param connection: An instance of SMPPServerProtocol """ bind_type = connection.bind_type self._binds[bind_type].remove(connection) - + def getMaxTransmitReceiveBindCount(self): return len(self._binds[pdu_types.CommandId.bind_transceiver]) + \ max(len(self._binds[pdu_types.CommandId.bind_transmitter]), - len(self._binds[pdu_types.CommandId.bind_receiver])) - - def getBindingCount(self): + len(self._binds[pdu_types.CommandId.bind_receiver])) + + def getBindingCount(self): return sum(len(v) for v in self._binds.values()) - + def getBindingCountByType(self): ret = {} - for key, value in self._binds.iteritems(): + for key, value in self._binds.items(): ret[key] = len(value) return ret def __len__(self): return self.getBindingCount() - + def __iter__(self): vals = [] [vals.extend(type) for type in self._binds.values()] return vals.__iter__() - + def getBindingCountForType(self, bind_type): """ Sum transceiver binds plus receiver or transmitter depending on this type @@ -193,7 +196,7 @@ def getBindingCountForType(self, bind_type): # Sum of transceiver binds plus existing binds of this type connections_count = sum([len(self._binds[bt]) for bt in (pdu_types.CommandId.bind_transceiver, bind_type)]) return connections_count - + def getNextBindingForDelivery(self): """ Messages inbound (MO) that are to be forwarded to @@ -213,7 +216,7 @@ def getNextBindingForDelivery(self): break else: binding = None - + # Otherwise send on the last trx/rx binding delivered on, as # long as it is still bound while binding is None and self._delivery_binding_history: @@ -223,8 +226,7 @@ def getNextBindingForDelivery(self): if _binding in self._binds[_binding.bind_type]: # If so then use it binding = _binding - + if binding is not None: self._delivery_binding_history.append(binding) return binding - diff --git a/smpp/twisted/tests/__init__.py b/smpp/twisted/tests/__init__.py deleted file mode 100644 index 5d41fcf..0000000 --- a/smpp/twisted/tests/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Copyright 2009-2010 Mozes, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either expressed or implied. - See the License for the specific language governing permissions and - limitations under the License. -""" \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..1ab78a9 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +""" +Twisted requires a package to run tests on +""" \ No newline at end of file diff --git a/smpp/twisted/tests/smsc_simulator.py b/tests/smsc_simulator.py similarity index 81% rename from smpp/twisted/tests/smsc_simulator.py rename to tests/smsc_simulator.py index 858d021..6ea04cc 100644 --- a/smpp/twisted/tests/smsc_simulator.py +++ b/tests/smsc_simulator.py @@ -13,46 +13,64 @@ See the License for the specific language governing permissions and limitations under the License. """ -import logging, struct, StringIO, binascii -from twisted.internet.protocol import Protocol, Factory -from twisted.internet import reactor, protocol -from smpp.pdu.operations import * +from io import BytesIO +import logging +import struct +import binascii + +from twisted.internet.protocol import Protocol +from smpp.pdu.operations import ( + GenericNack, + EnquireLink, + BindTransmitter, + BindTransceiver, + BindReceiver, + Unbind, + SubmitSM, + DataSM, + AlertNotification, + DeliverSM, + QuerySM, + Outbind +) from smpp.pdu.pdu_encoding import PDUEncoder -from smpp.pdu.pdu_types import * +from smpp.pdu.pdu_types import CommandStatus, PDURequest, AddrTon -LOG_CATEGORY="smpp.twisted.tests.smsc_simulator" +LOG_CATEGORY = "smpp.twisted.tests.smsc_simulator" -class BlackHoleSMSC( protocol.Protocol ): +class BlackHoleSMSC(Protocol): responseMap = {} - def __init__( self ): + def __init__(self): self.log = logging.getLogger(LOG_CATEGORY) - self.recvBuffer = "" + self.recvBuffer = b"" self.lastSeqNum = 0 self.encoder = PDUEncoder() - def dataReceived( self, data ): + 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: + 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 ) + self.rawMessageReceived(message) - def rawMessageReceived( self, message ): - return self.PDUReceived( self.encoder.decode( StringIO.StringIO(message) ) ) + def rawMessageReceived(self, message): + return self.PDUReceived( + self.encoder.decode( + BytesIO(message))) - def PDUReceived( self, pdu ): + 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 sendResponse(self, reqPDU, status): respPDU = reqPDU.requireAck(reqPDU.seqNum, status=status) self.sendPDU(respPDU) @@ -64,10 +82,11 @@ def sendPDU(self, pdu): # 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 ) - + self.transport.write(encoded) + + class HappySMSC(BlackHoleSMSC): - + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { @@ -79,22 +98,24 @@ def __init__(self): SubmitSM: self.handleSubmit, DataSM: self.handleData, } - + def handleSubmit(self, reqPDU): self.sendSuccessResponse(reqPDU) - + def handleData(self, reqPDU): self.sendSuccessResponse(reqPDU) + class AlertNotificationSMSC(HappySMSC): - + def handleData(self, reqPDU): HappySMSC.handleData(self, reqPDU) self.sendPDU(AlertNotification()) + class EnquireLinkEchoSMSC(HappySMSC): - - def __init__( self ): + + def __init__(self): HappySMSC.__init__(self) self.responseMap[EnquireLink] = self.echoEnquireLink @@ -102,163 +123,176 @@ def echoEnquireLink(self, reqPDU): self.sendSuccessResponse(reqPDU) self.sendPDU(EnquireLink()) + class NoResponseOnSubmitSMSC(HappySMSC): - + def handleSubmit(self, reqPDU): pass + class GenericNackNoSeqNumOnSubmitSMSC(HappySMSC): - + def handleSubmit(self, reqPDU): respPDU = GenericNack(status=CommandStatus.ESME_RINVCMDLEN) self.sendPDU(respPDU) + class GenericNackWithSeqNumOnSubmitSMSC(HappySMSC): - + def handleSubmit(self, reqPDU): respPDU = GenericNack(seqNum=reqPDU.seqNum, status=CommandStatus.ESME_RINVCMDID) self.sendPDU(respPDU) + class ErrorOnSubmitSMSC(HappySMSC): - + def handleSubmit(self, reqPDU): self.sendResponse(reqPDU, CommandStatus.ESME_RINVESMCLASS) + class UnbindOnSubmitSMSC(HappySMSC): - + def handleSubmit(self, reqPDU): self.sendPDU(Unbind()) + class UnbindNoResponseSMSC(HappySMSC): - - def __init__( self ): + + def __init__(self): HappySMSC.__init__(self) del self.responseMap[Unbind] - + + class BindErrorSMSC(BlackHoleSMSC): - - def __init__( self ): + + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { BindTransmitter: self.bindError, BindReceiver: self.bindError, BindTransceiver: self.bindError, } - + def bindError(self, reqPDU): self.sendResponse(reqPDU, CommandStatus.ESME_RBINDFAIL) + class BindErrorGenericNackSMSC(BlackHoleSMSC): - - def __init__( self ): + + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { BindTransmitter: self.bindError, BindReceiver: self.bindError, BindTransceiver: self.bindError, } - + def bindError(self, reqPDU): respPDU = GenericNack(reqPDU.seqNum) respPDU.status = CommandStatus.ESME_RINVCMDID self.sendPDU(respPDU) - + + class CommandLengthTooShortSMSC(BlackHoleSMSC): - - def __init__( self ): + + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { BindTransmitter: self.sendInvalidCommandLengthPDUAfterBind, BindReceiver: self.sendInvalidCommandLengthPDUAfterBind, BindTransceiver: self.sendInvalidCommandLengthPDUAfterBind, } - + def sendInvalidCommandLengthPDUAfterBind(self, reqPDU): self.sendSuccessResponse(reqPDU) unbind = Unbind() encoded = self.encoder.encode(unbind) hexEncoded = binascii.b2a_hex(encoded) - #Overwrite the command length (first octet) - badCmdLenHex = '0000000f' + # Overwrite the command length (first octet) + badCmdLenHex = b'0000000f' badHexEncoded = badCmdLenHex + hexEncoded[len(badCmdLenHex):] self.log.debug("Sending PDU with cmd len too small [%s]" % badHexEncoded) badEncoded = binascii.a2b_hex(badHexEncoded) self.transport.write(badEncoded) + class CommandLengthTooLongSMSC(BlackHoleSMSC): - - def __init__( self ): + + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { BindTransmitter: self.sendInvalidCommandLengthPDUAfterBind, BindReceiver: self.sendInvalidCommandLengthPDUAfterBind, BindTransceiver: self.sendInvalidCommandLengthPDUAfterBind, } - + def sendInvalidCommandLengthPDUAfterBind(self, reqPDU): self.sendSuccessResponse(reqPDU) unbind = Unbind() encoded = self.encoder.encode(unbind) hexEncoded = binascii.b2a_hex(encoded) - #Overwrite the command length (first octet) - badCmdLenHex = '0000ffff' + # Overwrite the command length (first octet) + badCmdLenHex = b'0000ffff' badHexEncoded = badCmdLenHex + hexEncoded[len(badCmdLenHex):] self.log.debug("Sending PDU with cmd len too large [%s]" % badHexEncoded) badEncoded = binascii.a2b_hex(badHexEncoded) self.transport.write(badEncoded) + class InvalidCommandIdSMSC(BlackHoleSMSC): - - def __init__( self ): + + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { BindTransmitter: self.sendInvalidCommandIdAfterBind, BindReceiver: self.sendInvalidCommandIdAfterBind, BindTransceiver: self.sendInvalidCommandIdAfterBind, } - + def sendInvalidCommandIdAfterBind(self, reqPDU): self.sendSuccessResponse(reqPDU) unbind = Unbind() encoded = self.encoder.encode(unbind) hexEncoded = binascii.b2a_hex(encoded) - #Overwrite the command id (second octet) - badCmdIdHex = 'f0000009' + # Overwrite the command id (second octet) + badCmdIdHex = b'f0000009' badHexEncoded = hexEncoded[:8] + badCmdIdHex + hexEncoded[8 + len(badCmdIdHex):] self.log.debug("Sending PDU with invalid cmd id [%s]" % badHexEncoded) badEncoded = binascii.a2b_hex(badHexEncoded) self.transport.write(badEncoded) - + + class NonFatalParseErrorSMSC(BlackHoleSMSC): seqNum = 2654 - - def __init__( self ): + + def __init__(self): BlackHoleSMSC.__init__(self) self.responseMap = { BindTransmitter: self.sendInvalidMessageAfterBind, BindReceiver: self.sendInvalidMessageAfterBind, BindTransceiver: self.sendInvalidMessageAfterBind, } - + def sendInvalidMessageAfterBind(self, reqPDU): self.sendSuccessResponse(reqPDU) - + pdu = QuerySM(seqNum=self.seqNum, - source_addr_ton=AddrTon.ABBREVIATED, - source_addr='1234' - ) + source_addr_ton=AddrTon.ABBREVIATED, + source_addr='1234' + ) encoded = self.encoder.encode(pdu) hexEncoded = binascii.b2a_hex(encoded) - #Overwrite the source_addr_ton param (18th octet) - badSrcAddrTonHex = '07' - badIdx = 17*2 + # Overwrite the source_addr_ton param (18th octet) + badSrcAddrTonHex = b'07' + badIdx = 17 * 2 badHexEncoded = hexEncoded[:badIdx] + badSrcAddrTonHex + hexEncoded[(badIdx + len(badSrcAddrTonHex)):] self.log.debug("Sending PDU with invalid source_addr_ton [%s]" % badHexEncoded) badEncoded = binascii.a2b_hex(badHexEncoded) self.transport.write(badEncoded) + class DeliverSMBeforeBoundSMSC(BlackHoleSMSC): - + def connectionMade(self): pdu = DeliverSM( source_addr='1234', @@ -266,36 +300,38 @@ def connectionMade(self): short_message='test', ) self.sendPDU(pdu) - + + class OutbindSMSC(HappySMSC): - - def __init__( self ): + + def __init__(self): HappySMSC.__init__(self) self.responseMap[BindReceiver] = self.sendDeliverSM - + def connectionMade(self): self.sendPDU(Outbind()) - + def sendDeliverSM(self, reqPDU): self.sendSuccessResponse(reqPDU) - + pdu = DeliverSM( source_addr='1234', destination_addr='4567', short_message='test', ) self.sendPDU(pdu) - + + class DeliverSMSMSC(HappySMSC): - - def __init__( self ): + + def __init__(self): HappySMSC.__init__(self) self.responseMap[BindReceiver] = self.sendDeliverSM self.responseMap[BindTransceiver] = self.sendDeliverSM - + def sendDeliverSM(self, reqPDU): self.sendSuccessResponse(reqPDU) - + pdu = DeliverSM( source_addr='1234', destination_addr='4567', @@ -303,15 +339,9 @@ def sendDeliverSM(self, reqPDU): ) self.sendPDU(pdu) + class DeliverSMAndUnbindSMSC(DeliverSMSMSC): - + def sendDeliverSM(self, reqPDU): DeliverSMSMSC.sendDeliverSM(self, reqPDU) self.sendPDU(Unbind()) - -if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG) - factory = Factory() - factory.protocol = BlackHoleSMSC - reactor.listenTCP(8007, factory) - reactor.run() \ No newline at end of file diff --git a/smpp/twisted/tests/smpp_client_test.py b/tests/test_smpp_client.py similarity index 90% rename from smpp/twisted/tests/smpp_client_test.py rename to tests/test_smpp_client.py index 72747dd..e1725a4 100644 --- a/smpp/twisted/tests/smpp_client_test.py +++ b/tests/test_smpp_client.py @@ -15,37 +15,72 @@ """ import logging import functools -from twisted.trial import unittest +from twisted.trial.unittest import TestCase from twisted.internet import error, reactor, defer -from twisted.internet.protocol import Factory -from twisted.python import log +from twisted.internet.protocol import Factory import mock +import sys + +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") +import tests.smsc_simulator as smsc_simulator +from smpp.twisted.config import SMPPClientConfig + from smpp.twisted.protocol import SMPPClientProtocol from smpp.twisted.client import SMPPClientTransmitter, SMPPClientReceiver, SMPPClientTransceiver, DataHandlerResponse, SMPPClientService -from smpp.twisted.tests.smsc_simulator import * -from smpp.pdu.error import * -from smpp.twisted.config import SMPPClientConfig -from smpp.pdu.operations import * -from smpp.pdu.pdu_types import * -class SimulatorTestCase(unittest.TestCase): - protocol = BlackHoleSMSC +from smpp.pdu.error import ( + SMPPClientConnectionCorruptedError, + PDUCorruptError, + PDUParseError, + SMPPClientSessionStateError, + SessionStateError, + SMPPProtocolError, + SMPPClientError, + SMPPError, + SMPPGenericNackTransactionError, + SMPPTransactionError, + SMPPRequestTimoutError, + SMPPSessionInitTimoutError +) +from smpp.pdu.operations import ( + GenericNack, + getPDUClass, + EnquireLink, + BindTransmitter, + BindTransceiver, + BindReceiver, + Unbind, + SubmitSM, + UnbindResp, + EnquireLinkResp, + DataSM, + AlertNotification, + DeliverSM, + QuerySMResp, + DataSMResp +) +from smpp.pdu.pdu_types import DeliveryFailureReason, CommandStatus + +class SimulatorTestCase(TestCase): + protocol = smsc_simulator.BlackHoleSMSC configArgs = {} - + def setUp(self): self.factory = Factory() self.factory.protocol = self.protocol self.port = reactor.listenTCP(0, self.factory) self.testPort = self.port.getHost().port - + args = self.configArgs.copy() args['host'] = self.configArgs.get('host', 'localhost') args['port'] = self.configArgs.get('port', self.testPort) args['username'] = self.configArgs.get('username', '') args['password'] = self.configArgs.get('password', '') - + self.config = SMPPClientConfig(**args) - + def tearDown(self): self.port.stopListening() @@ -53,7 +88,7 @@ class SessionInitTimeoutTestCase(SimulatorTestCase): configArgs = { 'sessionInitTimerSecs': 0.1, } - + def test_bind_transmitter_timeout(self): client = SMPPClientTransmitter(self.config) return self.assertFailure(client.connectAndBind(), SMPPSessionInitTimoutError) @@ -67,21 +102,21 @@ def test_bind_transceiver_timeout(self): return self.assertFailure(client.connectAndBind(), SMPPSessionInitTimoutError) class BindErrorTestCase(SimulatorTestCase): - protocol = BindErrorSMSC + protocol = smsc_simulator.BindErrorSMSC def test_bind_error(self): client = SMPPClientTransmitter(self.config) return self.assertFailure(client.connectAndBind(), SMPPTransactionError) class BindErrorGenericNackTestCase(SimulatorTestCase): - protocol = BindErrorGenericNackSMSC + protocol = smsc_simulator.BindErrorGenericNackSMSC def test_bind_error_generic_nack(self): client = SMPPClientTransmitter(self.config) return self.assertFailure(client.connectAndBind(), SMPPGenericNackTransactionError) class UnbindTimeoutTestCase(SimulatorTestCase): - protocol = UnbindNoResponseSMSC + protocol = smsc_simulator.UnbindNoResponseSMSC configArgs = { 'sessionInitTimerSecs': 0.1, } @@ -98,13 +133,13 @@ def test_unbind_timeout(self): self.assertFailure(self.unbindDeferred, SMPPSessionInitTimoutError), #asserts that unbind timed out ] ) - + def do_unbind(self, smpp): smpp.unbind().chainDeferred(self.unbindDeferred) return smpp class ResponseTimeoutTestCase(SimulatorTestCase): - protocol = NoResponseOnSubmitSMSC + protocol = smsc_simulator.NoResponseOnSubmitSMSC configArgs = { 'responseTimerSecs': 0.1, } @@ -126,7 +161,7 @@ def test_response_timeout(self): self.disconnectDeferred, #asserts that disconnect deferred was triggered ] ) - + def do_test_setup(self, smpp): self.smpp = smpp smpp.getDisconnectedDeferred().chainDeferred(self.disconnectDeferred) @@ -135,14 +170,14 @@ def do_test_setup(self, smpp): smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) return smpp - #Test unbind sent + #Test unbind sent def verify(self, result): self.assertEquals(1, self.smpp.sendPDU.call_count) sent = self.smpp.sendPDU.call_args[0][0] self.assertTrue(isinstance(sent, Unbind)) class InactivityTimeoutTestCase(SimulatorTestCase): - protocol = HappySMSC + protocol = smsc_simulator.HappySMSC configArgs = { 'inactivityTimerSecs': 0.1, } @@ -160,21 +195,21 @@ def test_inactivity_timeout(self): self.disconnectDeferred, #asserts that disconnect deferred was triggered ] ) - + def do_test_setup(self, smpp): self.smpp = smpp smpp.getDisconnectedDeferred().chainDeferred(self.disconnectDeferred) smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) return smpp - #Test unbind sent + #Test unbind sent def verify(self, result): self.assertEquals(1, self.smpp.sendPDU.call_count) sent = self.smpp.sendPDU.call_args[0][0] self.assertTrue(isinstance(sent, Unbind)) class ServerInitiatedUnbindTestCase(SimulatorTestCase): - protocol = UnbindOnSubmitSMSC + protocol = smsc_simulator.UnbindOnSubmitSMSC def setUp(self): SimulatorTestCase.setUp(self) @@ -191,21 +226,21 @@ def test_server_unbind(self): self.assertFailure(self.submitSMDeferred, SMPPClientSessionStateError), #asserts that outbound txn was canceled ] ) - + def mock_stuff(self, smpp): self.smpp = smpp smpp.getDisconnectedDeferred().chainDeferred(self.disconnectDeferred) smpp.sendDataRequest(SubmitSM(source_addr='mobileway', destination_addr='1208230', short_message='HELLO1')).chainDeferred(self.submitSMDeferred) smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) return smpp - + def verify(self, result): self.assertEquals(1, self.smpp.sendPDU.call_count) sent = self.smpp.sendPDU.call_args[0][0] self.assertEquals(UnbindResp(1), sent) class EnquireLinkTestCase(SimulatorTestCase): - protocol = EnquireLinkEchoSMSC + protocol = smsc_simulator.EnquireLinkEchoSMSC configArgs = { 'enquireLinkTimerSecs': 0.1, } @@ -216,40 +251,40 @@ def test_enquire_link(self): smpp = yield client.connect() #Assert that enquireLinkTimer is not yet active on connection self.assertEquals(None, smpp.enquireLinkTimer) - + bindDeferred = client.bind(smpp) #Assert that enquireLinkTimer is not yet active until bind is complete self.assertEquals(None, smpp.enquireLinkTimer) yield bindDeferred #Assert that enquireLinkTimer is now active after bind is complete self.assertNotEquals(None, smpp.enquireLinkTimer) - + #Wrap functions for tracking smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) smpp.PDUReceived = mock.Mock(wraps=smpp.PDUReceived) - + yield self.wait(0.25) - + self.verifyEnquireLink(smpp) - + #Assert that enquireLinkTimer is still active self.assertNotEquals(None, smpp.enquireLinkTimer) - + unbindDeferred = smpp.unbind() #Assert that enquireLinkTimer is no longer active after unbind is issued self.assertEquals(None, smpp.enquireLinkTimer) - + yield unbindDeferred #Assert that enquireLinkTimer is no longer active after unbind is complete self.assertEquals(None, smpp.enquireLinkTimer) yield smpp.disconnect() - + def wait(self, time_secs): finished = defer.Deferred() reactor.callLater(time_secs, finished.callback, None) return finished - + def verifyEnquireLink(self, smpp): self.assertEquals(4, smpp.sendPDU.call_count) self.assertEquals(4, smpp.PDUReceived.call_count) @@ -264,18 +299,18 @@ def verifyEnquireLink(self, smpp): self.assertEquals(EnquireLink(2), sent1) self.assertEquals(EnquireLinkResp(2), recv1) - + self.assertEquals(EnquireLink(1), recv2) self.assertEquals(EnquireLinkResp(1), sent2) - + self.assertEquals(EnquireLink(3), sent3) self.assertEquals(EnquireLinkResp(3), recv3) - + self.assertEquals(EnquireLink(2), recv4) self.assertEquals(EnquireLinkResp(2), sent4) - + class TransmitterLifecycleTestCase(SimulatorTestCase): - protocol = HappySMSC + protocol = smsc_simulator.HappySMSC def setUp(self): SimulatorTestCase.setUp(self) @@ -293,37 +328,37 @@ def test_unbind(self): self.disconnectDeferred, #asserts that disconnect was successful ] ) - + def do_lifecycle(self, smpp): smpp.getDisconnectedDeferred().chainDeferred(self.disconnectDeferred) smpp.sendDataRequest(SubmitSM()).chainDeferred(self.submitSMDeferred).addCallback(lambda result: smpp.unbindAndDisconnect().chainDeferred(self.unbindDeferred)) - return smpp + return smpp class AlertNotificationTestCase(SimulatorTestCase): - protocol = AlertNotificationSMSC + protocol = smsc_simulator.AlertNotificationSMSC @defer.inlineCallbacks def test_alert_notification(self): client = SMPPClientTransmitter(self.config) smpp = yield client.connectAndBind() - + alertNotificationDeferred = defer.Deferred() alertHandler = mock.Mock(wraps=lambda smpp, pdu: alertNotificationDeferred.callback(None)) smpp.setAlertNotificationHandler(alertHandler) - + sendDataDeferred = smpp.sendDataRequest(DataSM()) - + smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) smpp.PDUReceived = mock.Mock(wraps=smpp.PDUReceived) - + yield sendDataDeferred yield alertNotificationDeferred yield smpp.unbindAndDisconnect() - + self.assertEquals(1, alertHandler.call_count) self.assertEquals(smpp, alertHandler.call_args[0][0]) self.assertTrue(isinstance(alertHandler.call_args[0][1], AlertNotification)) - + self.assertEquals(1, smpp.sendPDU.call_count) self.assertEquals(3, smpp.PDUReceived.call_count) sent1 = smpp.sendPDU.call_args[0][0] @@ -336,8 +371,9 @@ def test_alert_notification(self): self.assertTrue(isinstance(recv3, UnbindResp)) class CommandLengthTooShortTestCase(SimulatorTestCase): - protocol = CommandLengthTooShortSMSC + protocol = smsc_simulator.CommandLengthTooShortSMSC + @defer.inlineCallbacks def test_generic_nack_on_invalid_cmd_len(self): client = SMPPClientTransceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() @@ -359,7 +395,7 @@ def mock_side_effect(self, msgSentDeferred, pdu): return mock.DEFAULT class CommandLengthTooLongTestCase(SimulatorTestCase): - protocol = CommandLengthTooLongSMSC + protocol = smsc_simulator.CommandLengthTooLongSMSC configArgs = { 'pduReadTimerSecs': 0.1, } @@ -368,79 +404,79 @@ class CommandLengthTooLongTestCase(SimulatorTestCase): def test_generic_nack_on_invalid_cmd_len(self): client = SMPPClientTransceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() - + msgSentDeferred = defer.Deferred() - + smpp.sendPDU = mock.Mock() smpp.sendPDU.side_effect = functools.partial(self.mock_side_effect, msgSentDeferred) - + yield msgSentDeferred - + yield smpp.disconnect() - + self.assertEquals(1, smpp.sendPDU.call_count) smpp.sendPDU.assert_called_with(GenericNack(status=CommandStatus.ESME_RINVCMDLEN)) - + def mock_side_effect(self, msgSentDeferred, pdu): msgSentDeferred.callback(None) return mock.DEFAULT class InvalidCommandIdTestCase(SimulatorTestCase): - protocol = InvalidCommandIdSMSC + protocol = smsc_simulator.InvalidCommandIdSMSC @defer.inlineCallbacks def test_generic_nack_on_invalid_cmd_id(self): client = SMPPClientTransceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() - + msgSentDeferred = defer.Deferred() - + smpp.sendPDU = mock.Mock() smpp.sendPDU.side_effect = functools.partial(self.mock_side_effect, msgSentDeferred) - + yield msgSentDeferred - + yield smpp.disconnect() - + self.assertEquals(1, smpp.sendPDU.call_count) smpp.sendPDU.assert_called_with(GenericNack(status=CommandStatus.ESME_RINVCMDID)) - + def mock_side_effect(self, msgSentDeferred, pdu): msgSentDeferred.callback(None) return mock.DEFAULT - + class NonFatalParseErrorTestCase(SimulatorTestCase): - protocol = NonFatalParseErrorSMSC + protocol = smsc_simulator.NonFatalParseErrorSMSC @defer.inlineCallbacks def test_nack_on_invalid_msg(self): client = SMPPClientTransceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() - + msgSentDeferred = defer.Deferred() - + smpp.sendPDU = mock.Mock() smpp.sendPDU.side_effect = functools.partial(self.mock_side_effect, msgSentDeferred) - + yield msgSentDeferred - + yield smpp.disconnect() - + self.assertEquals(1, smpp.sendPDU.call_count) smpp.sendPDU.assert_called_with(QuerySMResp(seqNum=self.protocol.seqNum, status=CommandStatus.ESME_RINVSRCTON)) - + def mock_side_effect(self, msgSentDeferred, pdu): msgSentDeferred.callback(None) return mock.DEFAULT - + class GenericNackNoSeqNumTestCase(SimulatorTestCase): - protocol = GenericNackNoSeqNumOnSubmitSMSC + protocol = smsc_simulator.GenericNackNoSeqNumOnSubmitSMSC @defer.inlineCallbacks def test_generic_nack_no_seq_num(self): client = SMPPClientTransceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() - + try: submitDeferred = smpp.sendDataRequest(SubmitSM()) smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) @@ -449,18 +485,18 @@ def test_generic_nack_no_seq_num(self): pass else: self.assertTrue(False, "SMPPClientConnectionCorruptedError not raised") - + #for nack with no seq num, the connection is corrupt so don't unbind() - self.assertEquals(0, smpp.sendPDU.call_count) - + self.assertEquals(0, smpp.sendPDU.call_count) + class GenericNackWithSeqNumTestCase(SimulatorTestCase): - protocol = GenericNackWithSeqNumOnSubmitSMSC + protocol = smsc_simulator.GenericNackWithSeqNumOnSubmitSMSC @defer.inlineCallbacks def test_generic_nack_no_seq_num(self): client = SMPPClientTransceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() - + try: yield smpp.sendDataRequest(SubmitSM()) except SMPPGenericNackTransactionError: @@ -469,9 +505,9 @@ def test_generic_nack_no_seq_num(self): self.assertTrue(False, "SMPPGenericNackTransactionError not raised") finally: yield smpp.unbindAndDisconnect() - + class ErrorOnSubmitTestCase(SimulatorTestCase): - protocol = ErrorOnSubmitSMSC + protocol = smsc_simulator.ErrorOnSubmitSMSC @defer.inlineCallbacks def test_error_on_submit(self): @@ -485,20 +521,20 @@ def test_error_on_submit(self): self.assertTrue(False, "SMPPTransactionError not raised") finally: yield smpp.unbindAndDisconnect() - + class ReceiverLifecycleTestCase(SimulatorTestCase): - protocol = DeliverSMAndUnbindSMSC + protocol = smsc_simulator.DeliverSMAndUnbindSMSC @defer.inlineCallbacks def test_receiver_lifecycle(self): client = SMPPClientReceiver(self.config, lambda smpp, pdu: None) smpp = yield client.connectAndBind() - + smpp.PDUReceived = mock.Mock(wraps=smpp.PDUReceived) smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) - + yield smpp.getDisconnectedDeferred() - + self.assertEquals(2, smpp.PDUReceived.call_count) self.assertEquals(2, smpp.sendPDU.call_count) recv1 = smpp.PDUReceived.call_args_list[0][0][0] @@ -511,18 +547,18 @@ def test_receiver_lifecycle(self): self.assertEquals(recv2.requireAck(recv2.seqNum), sent2) class ReceiverDataHandlerExceptionTestCase(SimulatorTestCase): - protocol = DeliverSMSMSC + protocol = smsc_simulator.DeliverSMSMSC @defer.inlineCallbacks def test_receiver_exception(self): client = SMPPClientReceiver(self.config, self.barf) smpp = yield client.connectAndBind() - + smpp.PDUReceived = mock.Mock(wraps=smpp.PDUReceived) smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) - + yield smpp.getDisconnectedDeferred() - + self.assertEquals(2, smpp.PDUReceived.call_count) self.assertEquals(2, smpp.sendPDU.call_count) recv1 = smpp.PDUReceived.call_args_list[0][0][0] @@ -533,23 +569,23 @@ def test_receiver_exception(self): self.assertEquals(recv1.requireAck(recv1.seqNum, CommandStatus.ESME_RX_T_APPN), sent1) self.assertTrue(isinstance(sent2, Unbind)) self.assertTrue(isinstance(recv2, UnbindResp)) - + def barf(self, smpp, pdu): raise ValueError('barf') - + class ReceiverDataHandlerBadResponseParamTestCase(SimulatorTestCase): - protocol = DeliverSMSMSC + protocol = smsc_simulator.DeliverSMSMSC @defer.inlineCallbacks def test_receiver_bad_resp_param(self): client = SMPPClientReceiver(self.config, self.respondBadParam) smpp = yield client.connectAndBind() - + smpp.PDUReceived = mock.Mock(wraps=smpp.PDUReceived) smpp.sendPDU = mock.Mock(wraps=smpp.sendPDU) - + yield smpp.getDisconnectedDeferred() - + self.assertEquals(2, smpp.PDUReceived.call_count) self.assertEquals(2, smpp.sendPDU.call_count) recv1 = smpp.PDUReceived.call_args_list[0][0][0] @@ -560,12 +596,12 @@ def test_receiver_bad_resp_param(self): self.assertEquals(recv1.requireAck(recv1.seqNum, CommandStatus.ESME_RX_T_APPN), sent1) self.assertTrue(isinstance(sent2, Unbind)) self.assertTrue(isinstance(recv2, UnbindResp)) - + def respondBadParam(self, smpp, pdu): return DataHandlerResponse(delivery_failure_reason=DeliveryFailureReason.PERMANENT_NETWORK_ERROR) class ReceiverUnboundErrorTestCase(SimulatorTestCase): - protocol = DeliverSMBeforeBoundSMSC + protocol = smsc_simulator.DeliverSMBeforeBoundSMSC def setUp(self): SimulatorTestCase.setUp(self) @@ -576,7 +612,7 @@ def test_receiver_exception(self): return self.assertFailure(bindDeferred, SessionStateError) class OutbindTestCase(SimulatorTestCase): - protocol = OutbindSMSC + protocol = smsc_simulator.OutbindSMSC def msgHandler(self, smpp, pdu): smpp.unbindAndDisconnect() @@ -601,14 +637,4 @@ def test_bind_transmitter_timeout(self): return defer.DeferredList([ self.assertFailure(startDeferred, SMPPSessionInitTimoutError), self.assertFailure(stopDeferred, SMPPSessionInitTimoutError), - ]) - -if __name__ == '__main__': - observer = log.PythonLoggingObserver() - observer.start() - logging.basicConfig(level=logging.DEBUG) - - import sys - from twisted.scripts import trial - sys.argv.extend([sys.argv[0]]) - trial.run() \ No newline at end of file + ]) \ No newline at end of file diff --git a/smpp/twisted/tests/smpp_protocol_test.py b/tests/test_smpp_protocol.py similarity index 92% rename from smpp/twisted/tests/smpp_protocol_test.py rename to tests/test_smpp_protocol.py index b302a2f..d96fe5c 100644 --- a/smpp/twisted/tests/smpp_protocol_test.py +++ b/tests/test_smpp_protocol.py @@ -13,22 +13,40 @@ See the License for the specific language governing permissions and limitations under the License. """ -import logging, binascii -from twisted.trial import unittest -from twisted.internet import error, reactor, defer -from twisted.python import log +import logging +import binascii +from twisted.trial.unittest import TestCase +from twisted.internet import error, defer from mock import Mock, sentinel -from smpp.pdu.error import * -from smpp.pdu.operations import * -from smpp.pdu.pdu_types import * +import sys + +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") +from smpp.pdu.error import ( + SMPPClientConnectionCorruptedError, + SMPPClientSessionStateError, + SMPPClientError +) +from smpp.pdu.operations import ( + SubmitSM, + GenericNack, + DeliverSM, + DeliverSMResp, + Unbind, + UnbindResp, + DataSM, + DataSMResp +) +from smpp.pdu.pdu_types import CommandStatus, AddrTon, AddrNpi, DeliveryFailureReason from smpp.twisted.config import SMPPClientConfig from smpp.twisted.protocol import SMPPClientProtocol, SMPPSessionStates, SMPPOutboundTxnResult, DataHandlerResponse class FakeClientError(SMPPClientError): pass -class ProtocolTestCase(unittest.TestCase): - +class ProtocolTestCase(TestCase): + def getProtocolObject(self): smpp = SMPPClientProtocol() config = SMPPClientConfig( @@ -39,10 +57,10 @@ def getProtocolObject(self): ) smpp.config = Mock(return_value=config) return smpp - + def test_corruptData(self): smpp = self.getProtocolObject() - self.assertEquals('', smpp.recvBuffer) + self.assertEquals(b'', smpp.recvBuffer) smpp.sendPDU = Mock() smpp.cancelOutboundTransactions = Mock() smpp.shutdown = Mock() @@ -57,7 +75,7 @@ def test_corruptData(self): nackResp = smpp.sendPDU.call_args[0][0] self.assertEquals(GenericNack(seqNum=None, status=CommandStatus.ESME_RINVCMDLEN), nackResp) #Causes new data received to be ignored - newDataHex = 'afc4' + newDataHex = b'afc4' smpp.dataReceived(binascii.a2b_hex(newDataHex)) self.assertEquals(newDataHex, binascii.b2a_hex(smpp.recvBuffer)) #Causes new data requests to fail immediately @@ -70,7 +88,7 @@ def test_corruptData(self): short_message='HELLO', ) return self.assertFailure(smpp.sendDataRequest(submitPdu), SMPPClientConnectionCorruptedError) - + def test_cancelOutboundTransactions(self): smpp = self.getProtocolObject() smpp.sendPDU = Mock() @@ -100,12 +118,12 @@ def test_cancelOutboundTransactions(self): self.assertFailure(d1, FakeClientError), self.assertFailure(d2, FakeClientError), ]) - + def test_finish_txns(self): smpp = self.getProtocolObject() smpp.sendPDU = Mock() smpp.sessionState = SMPPSessionStates.BOUND_TRX - + #setup outbound txns outPdu1 = SubmitSM( seqNum=98790, @@ -114,7 +132,7 @@ def test_finish_txns(self): short_message='HELLO1', ) outRespPdu1 = outPdu1.requireAck(seqNum=outPdu1.seqNum) - + outPdu2 = SubmitSM( seqNum=875, source_addr='mobileway', @@ -122,34 +140,34 @@ def test_finish_txns(self): short_message='HELLO1', ) outRespPdu2 = outPdu2.requireAck(seqNum=outPdu2.seqNum, status=CommandStatus.ESME_RINVSRCTON) - + outDeferred1 = smpp.startOutboundTransaction(outPdu1, 1) outDeferred2 = smpp.startOutboundTransaction(outPdu2, 1) - + finishOutTxns = smpp.finishOutboundTxns() - + #Simulate second txn having error smpp.endOutboundTransactionErr(outRespPdu2, FakeClientError('test')) #Assert txns not done yet self.assertFalse(finishOutTxns.called) - + #Simulate first txn finishing smpp.endOutboundTransaction(outRespPdu1) #Assert txns are all done self.assertTrue(finishOutTxns.called) - + return defer.DeferredList([ outDeferred1, self.assertFailure(outDeferred2, FakeClientError), finishOutTxns, ] ) - + def test_graceful_unbind(self): smpp = self.getProtocolObject() smpp.sendPDU = Mock() smpp.sessionState = SMPPSessionStates.BOUND_TRX - + #setup outbound txn outPdu = SubmitSM( seqNum=98790, @@ -167,46 +185,46 @@ def test_graceful_unbind(self): short_message='HELLO1', ) inDeferred = smpp.startInboundTransaction(inPdu) - + #Call unbind unbindDeferred = smpp.unbind() - + #Assert unbind request not sent and deferred not fired self.assertEquals(0, smpp.sendPDU.call_count) self.assertFalse(unbindDeferred.called) #Simulate inbound txn finishing smpp.endInboundTransaction(inPdu) - + #Assert unbind request not sent and deferred not fired self.assertEquals(0, smpp.sendPDU.call_count) self.assertFalse(unbindDeferred.called) - + #Simulate outbound txn finishing smpp.endOutboundTransaction(outRespPdu) - + #Assert unbind request was sent but deferred not yet fired self.assertEquals(1, smpp.sendPDU.call_count) sentPdu = smpp.sendPDU.call_args[0][0] self.assertTrue(isinstance(sentPdu, Unbind)) self.assertFalse(unbindDeferred.called) - + bindResp = UnbindResp(seqNum=sentPdu.seqNum) - + #Simulate unbind_resp smpp.endOutboundTransaction(bindResp) - + #Assert unbind deferred fired self.assertTrue(unbindDeferred.called) self.assertTrue(isinstance(unbindDeferred.result, SMPPOutboundTxnResult)) expectedResult = SMPPOutboundTxnResult(smpp, sentPdu, bindResp) self.assertEquals(expectedResult, unbindDeferred.result) - + def test_bind_when_not_in_open_state(self): smpp = self.getProtocolObject() smpp.sessionState = SMPPSessionStates.BOUND_TRX return self.assertFailure(smpp.bindAsTransmitter(), SMPPClientSessionStateError) - + def test_unbind_when_not_bound(self): smpp = self.getProtocolObject() smpp.sessionState = SMPPSessionStates.BIND_TX_PENDING @@ -216,13 +234,13 @@ def test_server_initiated_unbind_cancels_enquire_link_timer(self): smpp = self.getProtocolObject() smpp.sendResponse = Mock() smpp.disconnect = Mock() - + smpp.sessionState = SMPPSessionStates.BOUND_TRX smpp.activateEnquireLinkTimer() self.assertNotEquals(None, smpp.enquireLinkTimer) smpp.onPDURequest_unbind(Unbind()) self.assertEquals(None, smpp.enquireLinkTimer) - + def test_sendDataRequest_when_not_bound(self): smpp = self.getProtocolObject() smpp.sessionState = SMPPSessionStates.BIND_TX_PENDING @@ -232,7 +250,7 @@ def test_sendDataRequest_invalid_pdu(self): smpp = self.getProtocolObject() smpp.sessionState = SMPPSessionStates.BOUND_TRX return self.assertFailure(smpp.sendDataRequest(Unbind()), SMPPClientError) - + def test_data_handler_return_none(self): smpp = self.getProtocolObject() smpp.sendPDU = Mock() @@ -241,7 +259,7 @@ def test_data_handler_return_none(self): self.assertEquals(1, smpp.sendPDU.call_count) sent = smpp.sendPDU.call_args[0][0] self.assertEquals(DeliverSMResp(5), sent) - + def test_data_handler_return_status(self): smpp = self.getProtocolObject() smpp.sendPDU = Mock() @@ -269,14 +287,4 @@ def test_data_handler_return_junk(self): self.assertEquals(1, smpp.shutdown.call_count) self.assertEquals(1, smpp.sendPDU.call_count) sent = smpp.sendPDU.call_args[0][0] - self.assertEquals(DeliverSMResp(5, CommandStatus.ESME_RX_T_APPN), sent) - -if __name__ == '__main__': - observer = log.PythonLoggingObserver() - observer.start() - logging.basicConfig(level=logging.DEBUG) - - import sys - from twisted.scripts import trial - sys.argv.extend([sys.argv[0]]) - trial.run() \ No newline at end of file + self.assertEquals(DeliverSMResp(5, CommandStatus.ESME_RX_T_APPN), sent) \ No newline at end of file diff --git a/smpp/twisted/tests/test_smpp_server.py b/tests/test_smpp_server.py similarity index 93% rename from smpp/twisted/tests/test_smpp_server.py rename to tests/test_smpp_server.py index 93ec660..0b2c791 100644 --- a/smpp/twisted/tests/test_smpp_server.py +++ b/tests/test_smpp_server.py @@ -1,506 +1,508 @@ -import unittest -import logging -from twisted.web import resource -from twisted.internet import defer, reactor, task -from twisted.application import internet -from twisted.trial.unittest import TestCase -from twisted.cred.portal import IRealm -from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse -from twisted.cred.portal import Portal -from twisted.test import proto_helpers -from zope.interface import implements - -from smpp.twisted.config import SMPPServerConfig, SMPPClientConfig -from smpp.twisted.server import SMPPServerFactory, SMPPBindManager -from smpp.twisted.protocol import SMPPSessionStates, DataHandlerResponse -from smpp.twisted.client import SMPPClientTransceiver - -from smpp.pdu import pdu_types, operations, pdu_encoding - -import mock - -logging.basicConfig(level = logging.DEBUG) - -def _makeMockServerConnection(key, bind_type): - mk_server_cnxn = mock.Mock('mock svr cnxn') - mk_server_cnxn.system_id = key - mk_server_cnxn.bind_type = bind_type - return mk_server_cnxn - -class SMPPServerFactoryTests(unittest.TestCase): - - def setUp(self): - self.config = mock.Mock('mock smpp config') - self.config.systems = {'lala': {'max_bindings': 2}} - - def tearDown(self): - pass - - def test_canOpenNewConnection_transceiver(self): - server = SMPPServerFactory(self.config, None) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transceiver) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transceiver) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertFalse(can_bind, 'Should not be able to bind as two trx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertFalse(can_bind, 'Should not be able to bind as two trx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertFalse(can_bind, 'Should not be able to bind as two trx already bound') - - def test_canOpenNewConnection_transmitter(self): - server = SMPPServerFactory(self.config, None) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transmitter) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one tx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should still be able to bind as only one tx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should still be able to bind only bindings are tx') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transmitter) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertFalse(can_bind, 'Should not be able to bind as two tx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertFalse(can_bind, 'Should not be able to bind as two tx already bound') - # NOTE this one different - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should still be able to bind only bindings are tx') - - def test_canOpenNewConnection_receiver(self): - server = SMPPServerFactory(self.config, None) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_receiver) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one rx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should still be able to bind only bindings are rx') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one rx bound') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_receiver) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertFalse(can_bind, 'Should not be able to bind as two rx already bound') - # NOTE this one different - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should still be able to bind only bindings are rx') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertFalse(can_bind, 'Should not be able to bind as two rx already bound') - - def test_canOpenNewConnection_multitypes(self): - server = SMPPServerFactory(self.config, None) - - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transceiver) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transmitter) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertFalse(can_bind, 'Should not be able to bind as one trx and one tx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertFalse(can_bind, 'Should not be able to bind as one trx and one tx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertTrue(can_bind, 'Should still be able to bind as only one trx and one tx bound') - - mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_receiver) - server.addBoundConnection(mk_server_cnxn) - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) - self.assertFalse(can_bind, 'Should not be able to bind as one trx, one tx, and one rx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) - self.assertFalse(can_bind, 'Should not be able to bind as one trx, one tx, and one rx already bound') - can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) - self.assertFalse(can_bind, 'Should not be able to bind as one trx, one tx, and one rx already bound') - - -class SMPPBindManagerTests(unittest.TestCase): - - def test_getNextBindingForDelivery(self): - bm = SMPPBindManager('blah') - - # add an initial rx - mk_server_cnxn_rx1 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_receiver) - bm.addBinding(mk_server_cnxn_rx1) - - # Get the only rx - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx1, deliverer) - - # Get the only rx again - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx1, deliverer) - - # Add a new rx - mk_server_cnxn_rx2 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_receiver) - bm.addBinding(mk_server_cnxn_rx2) - - # Get the new rx - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx2, deliverer) - - # Expect the original rx again - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx1, deliverer) - - # Add a new tx - shouldn't affect deliverer selection at all - mk_server_cnxn_tx1 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_transmitter) - bm.addBinding(mk_server_cnxn_tx1) - - # Expect the 2nd rx again - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx2, deliverer) - - # Add a new trx - mk_server_cnxn_trx1 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_transceiver) - bm.addBinding(mk_server_cnxn_trx1) - - # Expect the new trx - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_trx1, deliverer) - - # Remove the 1st rx - bm.removeBinding(mk_server_cnxn_rx1) - - # Expect the 2nd rx again - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx2, deliverer) - - # Expect the new trx - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_trx1, deliverer) - - # Expect the 2nd rx again - deliverer = bm.getNextBindingForDelivery() - self.assertEqual(mk_server_cnxn_rx2, deliverer) - -class SMPPServerBaseTest(TestCase): - def _serviceHandler(self, system_id, smpp, pdu): - self.service_calls.append((system_id, smpp, pdu)) - return pdu_types.CommandStatus.ESME_ROK - - class SmppRealm(object): - implements(IRealm) - - def requestAvatar(self, avatarId, mind, *interfaces): - return ('SMPP', avatarId, lambda: None) - - def _bind(self): - self.proto.bind_type = pdu_types.CommandId.bind_transceiver - self.proto.sessionState = SMPPSessionStates.BOUND_TRX - self.proto.system_id = 'userA' - self.factory.addBoundConnection(self.proto) - -class SMPPServerTestCase(SMPPServerBaseTest): - - def setUp(self): - self.service_calls = [] - self.encoder = pdu_encoding.PDUEncoder() - self.smpp_config = SMPPServerConfig(msgHandler=self._serviceHandler, - systems={'userA': {"max_bindings": 2}} - ) - portal = Portal(self.SmppRealm()) - credential_checker = InMemoryUsernamePasswordDatabaseDontUse() - credential_checker.addUser('userA', 'valid') - portal.registerChecker(credential_checker) - self.factory = SMPPServerFactory(self.smpp_config, auth_portal=portal) - self.proto = self.factory.buildProtocol(('127.0.0.1', 0)) - self.tr = proto_helpers.StringTransport() - self.proto.makeConnection(self.tr) - - def tearDown(self): - self.proto.connectionLost('test end') - - def testTRXBindRequest(self): - pdu = operations.BindTransceiver( - system_id = 'userA', - password = 'valid', - seqNum = 1 - ) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.BindTransceiverResp(system_id='userA', seqNum=1) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - connection = self.factory.getBoundConnections('userA') - self.assertEqual(connection.system_id, 'userA') - self.assertEqual(connection._binds[pdu_types.CommandId.bind_transceiver][0], self.proto) - - def testTRXBindRequestInvalidSysId(self): - pdu = operations.BindTransceiver( - system_id = 'userB', - password = 'valid', - seqNum = 1 - ) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.BindTransceiverResp(system_id='userB', seqNum=1, status=pdu_types.CommandStatus.ESME_RINVPASWD) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - connection = self.factory.getBoundConnections('userA') - self.assertEqual(connection, None) - connection = self.factory.getBoundConnections('userB') - self.assertEqual(connection, None) - - def testTRXBindRequestInvalidPassword(self): - pdu = operations.BindTransceiver( - system_id = 'userA', - password = 'invalid', - seqNum = 1 - ) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.BindTransceiverResp(system_id='userA', seqNum=1, status=pdu_types.CommandStatus.ESME_RINVPASWD) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - connection = self.factory.getBoundConnections('userA') - self.assertEqual(connection, None) - - def testTransmitterBindRequest(self): - system_id = 'userA' - pdu = operations.BindTransmitter( - system_id = system_id, - password = 'valid', - seqNum = 1 - ) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.BindTransmitterResp(system_id='userA', seqNum=1) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - self.tr.clear() - connection = self.factory.getBoundConnections(system_id) - self.assertEqual(connection.system_id, system_id) - self.assertEqual(connection._binds[pdu_types.CommandId.bind_transmitter][0], self.proto) - bind_manager = self.factory.getBoundConnections(system_id) - delivery_binding = bind_manager.getNextBindingForDelivery() - self.assertTrue(delivery_binding is None) - - def testReceiverBindRequest(self): - system_id = 'userA' - pdu = operations.BindReceiver( - system_id = system_id, - password = 'valid', - seqNum = 1 - ) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.BindReceiverResp(system_id='userA', seqNum=1) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - self.tr.clear() - connection = self.factory.getBoundConnections(system_id) - self.assertEqual(connection.system_id, system_id) - self.assertEqual(connection._binds[pdu_types.CommandId.bind_receiver][0], self.proto) - bind_manager = self.factory.getBoundConnections(system_id) - delivery_binding = bind_manager.getNextBindingForDelivery() - self.assertTrue(delivery_binding is not None) - # TODO Identify what should be returned here - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.SubmitSMResp(seqNum=6) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - - - def testUnboundSubmitRequest(self): - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=1) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.SubmitSMResp(status=pdu_types.CommandStatus.ESME_RINVBNDSTS, seqNum=1) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - - def testUnboundSubmitRequest(self): - pdu = operations.EnquireLink(seqNum = 576) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.EnquireLinkResp(status=pdu_types.CommandStatus.ESME_RINVBNDSTS, seqNum=576) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - - def testTRXUnbindRequest(self): - self._bind() - pdu = operations.Unbind(seqNum = 346) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.UnbindResp(seqNum=346) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - connection = self.factory.getBoundConnections('userA') - self.assertEqual(connection.system_id, 'userA') - # Still in list of binds as the connection has not been closed yet. is removed after test tearDown - self.assertEqual(connection._binds[pdu_types.CommandId.bind_transceiver][0].sessionState, SMPPSessionStates.UNBOUND) - - def testTRXSubmitSM(self): - self._bind() - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.SubmitSMResp(seqNum=6) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - system_id, smpp, pdu_notified = self.service_calls.pop() - self.assertEqual(system_id, self.proto.system_id) - self.assertEqual(pdu.params['short_message'], pdu_notified.params['short_message']) - self.assertEqual(pdu.params['source_addr'], pdu_notified.params['source_addr']) - self.assertEqual(pdu.params['destination_addr'], pdu_notified.params['destination_addr']) - - def testTRXDataSM(self): - self._bind() - pdu = operations.DataSM(source_addr='t1', destination_addr='1208230', short_message='tests', seqNum=6) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.DataSMResp(seqNum=6) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - system_id, smpp, pdu_notified = self.service_calls.pop() - self.assertEqual(system_id, self.proto.system_id) - - def testTRXQuerySM(self): - def _serviceHandler(system_id, smpp, pdu): - self.service_calls.append((system_id, smpp, pdu)) - return DataHandlerResponse(status=pdu_types.CommandStatus.ESME_ROK, - message_id='tests', - final_date=None, - message_state=pdu_types.MessageState.ACCEPTED, - error_code=0) - self.proto.dataRequestHandler = lambda *args, **kwargs: _serviceHandler(self.proto.system_id, *args, **kwargs) - self._bind() - pdu = operations.QuerySM(message_id='tests', source_addr='t1', seqNum=23) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.QuerySMResp(message_id='tests', error_code=0, final_date=None, message_state=pdu_types.MessageState.ACCEPTED ,seqNum=23) - # Does not work as application using library must reply correctly. - print self.tr.value() - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - system_id, smpp, pdu_notified = self.service_calls.pop() - self.assertEqual(system_id, self.proto.system_id) - - def testRecievePduWhileUnbindPending(self): - self._bind() - self.proto.unbind() - expected_pdu = operations.Unbind(seqNum=1) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - self.tr.clear() - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.SubmitSMResp(seqNum=6) - self.assertEqual(self.tr.value(), '') - pdu = operations.UnbindResp(seqNum=1) - self.proto.dataReceived(self.encoder.encode(pdu)) - - def testTRXClientUnbindRequestAfterSubmit(self): - d = defer.Deferred() - def _serviceHandler(system_id, smpp, pdu): - logging.debug("%s, %s, %s", system_id, smpp, pdu) - return d - self.proto.dataRequestHandler = lambda *args, **kwargs: _serviceHandler(self.proto.system_id, *args, **kwargs) - self._bind() - - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=1) - self.proto.dataReceived(self.encoder.encode(pdu)) - pdu = operations.Unbind(seqNum = 52) - self.proto.dataReceived(self.encoder.encode(pdu)) - - #All PDU requests should fail now. - #Once we fire this we should get our Submit Resp and the unbind Resp - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='goodbye', seqNum=5) - self.proto.dataReceived(self.encoder.encode(pdu)) - - unbind_resp_pdu = operations.UnbindResp(seqNum=52) - submit_fail_pdu = operations.SubmitSMResp(status=pdu_types.CommandStatus.ESME_RINVBNDSTS, seqNum=5) - # We should have a reply here as our service handler should not be called - self.assertEqual(self.tr.value(), self.encoder.encode(submit_fail_pdu)) - self.tr.clear() - - d.callback(pdu_types.CommandStatus.ESME_ROK) - #Then we should get our initial message response and the unbind response - expected_pdu = operations.SubmitSMResp(seqNum=1) - self.assertEqual(self.tr.value(), '%s%s' % (self.encoder.encode(expected_pdu), self.encoder.encode(unbind_resp_pdu))) - - def testTRXServerUnbindRequestAfterSubmit(self): - deferreds = [] - def _serviceHandler(system_id, smpp, pdu): - d = defer.Deferred() - deferreds.append(d) - logging.debug("%s, %s, %s", system_id, smpp, pdu) - return d - self.proto.dataRequestHandler = lambda *args, **kwargs: _serviceHandler(self.proto.system_id, *args, **kwargs) - self._bind() - - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=1) - self.proto.dataReceived(self.encoder.encode(pdu)) - unbind_d = self.proto.unbind() - print self.tr.value() - - pdu2 = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO2', seqNum=2) - self.proto.dataReceived(self.encoder.encode(pdu)) - - self.assertEqual(1, len(deferreds)) - self.assertEqual(self.tr.value(), '') - self.tr.clear() - deferreds[-1].callback(pdu_types.CommandStatus.ESME_ROK) - deferreds = deferreds[:-1] - submit_resp_pdu = operations.SubmitSMResp(seqNum=1) - - unbind_pdu = operations.Unbind(seqNum=1) - # We should have a reply here as our service handler should not be called - self.assertEqual(self.tr.value(), '%s%s' % (self.encoder.encode(submit_resp_pdu), self.encoder.encode(unbind_pdu))) - self.tr.clear() - pdu = operations.UnbindResp(seqNum=1) - self.proto.dataReceived(self.encoder.encode(pdu)) - -class SMPPServerTimeoutTestCase(SMPPServerBaseTest): - - def setUp(self): - self.service_calls = [] - self.clock = task.Clock() - self.encoder = pdu_encoding.PDUEncoder() - self.smpp_config = SMPPServerConfig(msgHandler=self._serviceHandler, - systems={'userA': {"max_bindings": 2}}, - enquireLinkTimerSecs=0.1, - responseTimerSecs=0.1 - ) - portal = Portal(self.SmppRealm()) - credential_checker = InMemoryUsernamePasswordDatabaseDontUse() - credential_checker.addUser('userA', 'valid') - portal.registerChecker(credential_checker) - self.factory = SMPPServerFactory(self.smpp_config, auth_portal=portal) - self.proto = self.factory.buildProtocol(('127.0.0.1', 0)) - self.proto.callLater = self.clock.callLater - self.tr = proto_helpers.StringTransport() - self.proto.makeConnection(self.tr) - - def testEnquireTimeout(self): - self._bind() - pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) - self.proto.dataReceived(self.encoder.encode(pdu)) - expected_pdu = operations.SubmitSMResp(seqNum=6) - self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) - self.service_calls.pop() - self.tr.clear() - self.clock.advance(0.1) - self.clock.advance(0.1) - self.assertEqual(self.proto.sessionState, SMPPSessionStates.UNBIND_PENDING) +import unittest +import logging +from twisted.internet import defer, task +from twisted.trial.unittest import TestCase +from twisted.cred.portal import IRealm +from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse +from twisted.cred.portal import Portal +from twisted.test import proto_helpers +from zope.interface import implementer +import sys + +sys.path.append(".") +sys.path.append("..") +sys.path.append("../..") +from smpp.twisted.config import SMPPServerConfig, SMPPClientConfig +from smpp.twisted.server import SMPPServerFactory, SMPPBindManager +from smpp.twisted.protocol import SMPPSessionStates, DataHandlerResponse +from smpp.twisted.client import SMPPClientTransceiver + +from smpp.pdu import pdu_types, operations, pdu_encoding + +import mock + +logging.basicConfig(level = logging.DEBUG) + +def _makeMockServerConnection(key, bind_type): + mk_server_cnxn = mock.Mock('mock svr cnxn') + mk_server_cnxn.system_id = key + mk_server_cnxn.bind_type = bind_type + return mk_server_cnxn + +class SMPPServerFactoryTests(TestCase): + + def setUp(self): + self.config = mock.Mock('mock smpp config') + self.config.systems = {'lala': {'max_bindings': 2}} + + def tearDown(self): + pass + + def test_canOpenNewConnection_transceiver(self): + server = SMPPServerFactory(self.config, None) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transceiver) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transceiver) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertFalse(can_bind, 'Should not be able to bind as two trx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertFalse(can_bind, 'Should not be able to bind as two trx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertFalse(can_bind, 'Should not be able to bind as two trx already bound') + + def test_canOpenNewConnection_transmitter(self): + server = SMPPServerFactory(self.config, None) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transmitter) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one tx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should still be able to bind as only one tx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should still be able to bind only bindings are tx') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transmitter) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertFalse(can_bind, 'Should not be able to bind as two tx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertFalse(can_bind, 'Should not be able to bind as two tx already bound') + # NOTE this one different + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should still be able to bind only bindings are tx') + + def test_canOpenNewConnection_receiver(self): + server = SMPPServerFactory(self.config, None) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_receiver) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one rx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should still be able to bind only bindings are rx') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one rx bound') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_receiver) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertFalse(can_bind, 'Should not be able to bind as two rx already bound') + # NOTE this one different + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should still be able to bind only bindings are rx') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertFalse(can_bind, 'Should not be able to bind as two rx already bound') + + def test_canOpenNewConnection_multitypes(self): + server = SMPPServerFactory(self.config, None) + + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should be able to bind trx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should be able to bind tx as none bound yet') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should be able to bind rx as none bound yet') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transceiver) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx bound') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_transmitter) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertFalse(can_bind, 'Should not be able to bind as one trx and one tx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertFalse(can_bind, 'Should not be able to bind as one trx and one tx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertTrue(can_bind, 'Should still be able to bind as only one trx and one tx bound') + + mk_server_cnxn = _makeMockServerConnection('lala', pdu_types.CommandId.bind_receiver) + server.addBoundConnection(mk_server_cnxn) + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transceiver) + self.assertFalse(can_bind, 'Should not be able to bind as one trx, one tx, and one rx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_transmitter) + self.assertFalse(can_bind, 'Should not be able to bind as one trx, one tx, and one rx already bound') + can_bind = server.canOpenNewConnection('lala', pdu_types.CommandId.bind_receiver) + self.assertFalse(can_bind, 'Should not be able to bind as one trx, one tx, and one rx already bound') + + +class SMPPBindManagerTests(TestCase): + + def test_getNextBindingForDelivery(self): + bm = SMPPBindManager('blah') + + # add an initial rx + mk_server_cnxn_rx1 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_receiver) + bm.addBinding(mk_server_cnxn_rx1) + + # Get the only rx + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx1, deliverer) + + # Get the only rx again + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx1, deliverer) + + # Add a new rx + mk_server_cnxn_rx2 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_receiver) + bm.addBinding(mk_server_cnxn_rx2) + + # Get the new rx + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx2, deliverer) + + # Expect the original rx again + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx1, deliverer) + + # Add a new tx - shouldn't affect deliverer selection at all + mk_server_cnxn_tx1 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_transmitter) + bm.addBinding(mk_server_cnxn_tx1) + + # Expect the 2nd rx again + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx2, deliverer) + + # Add a new trx + mk_server_cnxn_trx1 = _makeMockServerConnection('blah', pdu_types.CommandId.bind_transceiver) + bm.addBinding(mk_server_cnxn_trx1) + + # Expect the new trx + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_trx1, deliverer) + + # Remove the 1st rx + bm.removeBinding(mk_server_cnxn_rx1) + + # Expect the 2nd rx again + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx2, deliverer) + + # Expect the new trx + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_trx1, deliverer) + + # Expect the 2nd rx again + deliverer = bm.getNextBindingForDelivery() + self.assertEqual(mk_server_cnxn_rx2, deliverer) + +class SMPPServerBaseTest(TestCase): + def _serviceHandler(self, system_id, smpp, pdu): + self.service_calls.append((system_id, smpp, pdu)) + return pdu_types.CommandStatus.ESME_ROK + + @implementer(IRealm) + class SmppRealm: + + def requestAvatar(self, avatarId, mind, *interfaces): + return ('SMPP', avatarId, lambda: None) + + def _bind(self): + self.proto.bind_type = pdu_types.CommandId.bind_transceiver + self.proto.sessionState = SMPPSessionStates.BOUND_TRX + self.proto.system_id = 'userA' + self.factory.addBoundConnection(self.proto) + +class SMPPServerTestCase(SMPPServerBaseTest): + + def setUp(self): + self.service_calls = [] + self.encoder = pdu_encoding.PDUEncoder() + self.smpp_config = SMPPServerConfig(msgHandler=self._serviceHandler, + systems={'userA': {"max_bindings": 2}} + ) + portal = Portal(self.SmppRealm()) + credential_checker = InMemoryUsernamePasswordDatabaseDontUse() + credential_checker.addUser('userA', 'valid') + portal.registerChecker(credential_checker) + self.factory = SMPPServerFactory(self.smpp_config, auth_portal=portal) + self.proto = self.factory.buildProtocol(('127.0.0.1', 0)) + self.tr = proto_helpers.StringTransport() + self.proto.makeConnection(self.tr) + + def tearDown(self): + self.proto.connectionLost('test end') + + def testTRXBindRequest(self): + pdu = operations.BindTransceiver( + system_id = 'userA', + password = 'valid', + seqNum = 1 + ) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.BindTransceiverResp(system_id='userA', seqNum=1) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + connection = self.factory.getBoundConnections('userA') + self.assertEqual(connection.system_id, 'userA') + self.assertEqual(connection._binds[pdu_types.CommandId.bind_transceiver][0], self.proto) + + def testTRXBindRequestInvalidSysId(self): + pdu = operations.BindTransceiver( + system_id = 'userB', + password = 'valid', + seqNum = 1 + ) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.BindTransceiverResp(system_id='userB', seqNum=1, status=pdu_types.CommandStatus.ESME_RINVSYSID) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + connection = self.factory.getBoundConnections('userA') + self.assertEqual(connection, None) + connection = self.factory.getBoundConnections('userB') + self.assertEqual(connection, None) + + def testTRXBindRequestInvalidPassword(self): + pdu = operations.BindTransceiver( + system_id = 'userA', + password = 'invalid', + seqNum = 1 + ) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.BindTransceiverResp(system_id='userA', seqNum=1, status=pdu_types.CommandStatus.ESME_RINVPASWD) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + connection = self.factory.getBoundConnections('userA') + self.assertEqual(connection, None) + + def testTransmitterBindRequest(self): + system_id = 'userA' + pdu = operations.BindTransmitter( + system_id = system_id, + password = 'valid', + seqNum = 1 + ) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.BindTransmitterResp(system_id='userA', seqNum=1) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + self.tr.clear() + connection = self.factory.getBoundConnections(system_id) + self.assertEqual(connection.system_id, system_id) + self.assertEqual(connection._binds[pdu_types.CommandId.bind_transmitter][0], self.proto) + bind_manager = self.factory.getBoundConnections(system_id) + delivery_binding = bind_manager.getNextBindingForDelivery() + self.assertTrue(delivery_binding is None) + + def testReceiverBindRequest(self): + system_id = 'userA' + pdu = operations.BindReceiver( + system_id = system_id, + password = 'valid', + seqNum = 1 + ) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.BindReceiverResp(system_id='userA', seqNum=1) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + self.tr.clear() + connection = self.factory.getBoundConnections(system_id) + self.assertEqual(connection.system_id, system_id) + self.assertEqual(connection._binds[pdu_types.CommandId.bind_receiver][0], self.proto) + bind_manager = self.factory.getBoundConnections(system_id) + delivery_binding = bind_manager.getNextBindingForDelivery() + self.assertTrue(delivery_binding is not None) + # TODO Identify what should be returned here + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.SubmitSMResp(seqNum=6) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + + + def testUnboundSubmitRequest(self): + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=1) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.SubmitSMResp(status=pdu_types.CommandStatus.ESME_RINVBNDSTS, seqNum=1) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + + def testUnboundSubmitRequest2(self): + pdu = operations.EnquireLink(seqNum = 576) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.EnquireLinkResp(status=pdu_types.CommandStatus.ESME_RINVBNDSTS, seqNum=576) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + + def testTRXUnbindRequest(self): + self._bind() + pdu = operations.Unbind(seqNum = 346) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.UnbindResp(seqNum=346) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + connection = self.factory.getBoundConnections('userA') + self.assertEqual(connection.system_id, 'userA') + # Still in list of binds as the connection has not been closed yet. is removed after test tearDown + self.assertEqual(connection._binds[pdu_types.CommandId.bind_transceiver][0].sessionState, SMPPSessionStates.UNBOUND) + + def testTRXSubmitSM(self): + self._bind() + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.SubmitSMResp(seqNum=6) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + system_id, smpp, pdu_notified = self.service_calls.pop() + self.assertEqual(system_id, self.proto.system_id) + self.assertEqual(pdu.params['short_message'], pdu_notified.params['short_message']) + self.assertEqual(pdu.params['source_addr'], pdu_notified.params['source_addr']) + self.assertEqual(pdu.params['destination_addr'], pdu_notified.params['destination_addr']) + + def testTRXDataSM(self): + self._bind() + pdu = operations.DataSM(source_addr='t1', destination_addr='1208230', short_message='tests', seqNum=6) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.DataSMResp(seqNum=6) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + system_id, smpp, pdu_notified = self.service_calls.pop() + self.assertEqual(system_id, self.proto.system_id) + + def testTRXQuerySM(self): + def _serviceHandler(system_id, smpp, pdu): + self.service_calls.append((system_id, smpp, pdu)) + return DataHandlerResponse(status=pdu_types.CommandStatus.ESME_ROK, + message_id='tests', + final_date=None, + message_state=pdu_types.MessageState.ACCEPTED, + error_code=0) + self.proto.dataRequestHandler = lambda *args, **kwargs: _serviceHandler(self.proto.system_id, *args, **kwargs) + self._bind() + pdu = operations.QuerySM(message_id='tests', source_addr='t1', seqNum=23) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.QuerySMResp(message_id='tests', error_code=0, final_date=None, message_state=pdu_types.MessageState.ACCEPTED ,seqNum=23) + # Does not work as application using library must reply correctly. + print(self.tr.value()) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + system_id, smpp, pdu_notified = self.service_calls.pop() + self.assertEqual(system_id, self.proto.system_id) + + def testRecievePduWhileUnbindPending(self): + self._bind() + self.proto.unbind() + expected_pdu = operations.Unbind(seqNum=1) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + self.tr.clear() + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.SubmitSMResp(seqNum=6) + self.assertEqual(self.tr.value(), b'') + pdu = operations.UnbindResp(seqNum=1) + self.proto.dataReceived(self.encoder.encode(pdu)) + + def testTRXClientUnbindRequestAfterSubmit(self): + d = defer.Deferred() + def _serviceHandler(system_id, smpp, pdu): + logging.debug("%s, %s, %s", system_id, smpp, pdu) + return d + self.proto.dataRequestHandler = lambda *args, **kwargs: _serviceHandler(self.proto.system_id, *args, **kwargs) + self._bind() + + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=1) + self.proto.dataReceived(self.encoder.encode(pdu)) + pdu = operations.Unbind(seqNum = 52) + self.proto.dataReceived(self.encoder.encode(pdu)) + + #All PDU requests should fail now. + #Once we fire this we should get our Submit Resp and the unbind Resp + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='goodbye', seqNum=5) + self.proto.dataReceived(self.encoder.encode(pdu)) + + unbind_resp_pdu = operations.UnbindResp(seqNum=52) + submit_fail_pdu = operations.SubmitSMResp(status=pdu_types.CommandStatus.ESME_RINVBNDSTS, seqNum=5) + # We should have a reply here as our service handler should not be called + self.assertEqual(self.tr.value(), self.encoder.encode(submit_fail_pdu)) + self.tr.clear() + + d.callback(pdu_types.CommandStatus.ESME_ROK) + #Then we should get our initial message response and the unbind response + expected_pdu = operations.SubmitSMResp(seqNum=1) + self.assertEqual(self.tr.value(), b'%s%s' % (self.encoder.encode(expected_pdu), self.encoder.encode(unbind_resp_pdu))) + + def testTRXServerUnbindRequestAfterSubmit(self): + deferreds = [] + def _serviceHandler(system_id, smpp, pdu): + d = defer.Deferred() + deferreds.append(d) + logging.debug("%s, %s, %s", system_id, smpp, pdu) + return d + self.proto.dataRequestHandler = lambda *args, **kwargs: _serviceHandler(self.proto.system_id, *args, **kwargs) + self._bind() + + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=1) + self.proto.dataReceived(self.encoder.encode(pdu)) + unbind_d = self.proto.unbind() + print(self.tr.value()) + + pdu2 = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO2', seqNum=2) + self.proto.dataReceived(self.encoder.encode(pdu)) + + self.assertEqual(1, len(deferreds)) + self.assertEqual(self.tr.value(), b'') + self.tr.clear() + deferreds[-1].callback(pdu_types.CommandStatus.ESME_ROK) + deferreds = deferreds[:-1] + submit_resp_pdu = operations.SubmitSMResp(seqNum=1) + + unbind_pdu = operations.Unbind(seqNum=1) + # We should have a reply here as our service handler should not be called + self.assertEqual(self.tr.value(), b'%s%s' % (self.encoder.encode(submit_resp_pdu), self.encoder.encode(unbind_pdu))) + self.tr.clear() + pdu = operations.UnbindResp(seqNum=1) + self.proto.dataReceived(self.encoder.encode(pdu)) + +class SMPPServerTimeoutTestCase(SMPPServerBaseTest): + + def setUp(self): + self.service_calls = [] + self.clock = task.Clock() + self.encoder = pdu_encoding.PDUEncoder() + self.smpp_config = SMPPServerConfig(msgHandler=self._serviceHandler, + systems={'userA': {"max_bindings": 2}}, + enquireLinkTimerSecs=0.1, + responseTimerSecs=0.1 + ) + portal = Portal(self.SmppRealm()) + credential_checker = InMemoryUsernamePasswordDatabaseDontUse() + credential_checker.addUser('userA', 'valid') + portal.registerChecker(credential_checker) + self.factory = SMPPServerFactory(self.smpp_config, auth_portal=portal) + self.proto = self.factory.buildProtocol(('127.0.0.1', 0)) + self.proto.callLater = self.clock.callLater + self.tr = proto_helpers.StringTransport() + self.proto.makeConnection(self.tr) + + def testEnquireTimeout(self): + self._bind() + pdu = operations.SubmitSM(source_addr='t1', destination_addr='1208230', short_message='HELLO', seqNum=6) + self.proto.dataReceived(self.encoder.encode(pdu)) + expected_pdu = operations.SubmitSMResp(seqNum=6) + self.assertEqual(self.tr.value(), self.encoder.encode(expected_pdu)) + self.service_calls.pop() + self.tr.clear() + self.clock.advance(0.1) + self.clock.advance(0.1) + self.assertEqual(self.proto.sessionState, SMPPSessionStates.UNBIND_PENDING)