diff --git a/.circleci/config.yml b/.circleci/config.yml index d7d80ca..eca5107 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,21 +3,26 @@ workflows: version: 2 test: jobs: + - test-3.13 + - test-3.12 + - test-3.11 + - test-3.10 - test-3.9 - - test-3.8 - - test-3.7 - - test-3.6 - - test-3.5 - - test-3.4 - test-2.7 - test-pypy3 - test-pypy2 jobs: - test-3.9: &test-template + test-3.13: &test-template docker: - - image: python:3.9 # We run one test in non-alpine environment, just in case + - image: python:3.13 # We run one test in non-alpine environment, just in case working_directory: ~/work steps: + - run: + name: Ensure SSH + command: | + apk add --update openssh-client git || { + apt-get update && apt-get install -y openssh-client git + } - checkout - run: name: Install dependencies @@ -30,32 +35,28 @@ jobs: name: Install project command: | . venv/bin/activate - pip install -e .[tests] + pip install -e '.[tests]' - run: name: Run tests command: | . venv/bin/activate pytest -v - test-3.8: - <<: *test-template - docker: - - image: python:3.8-alpine - test-3.7: + test-3.12: <<: *test-template docker: - - image: python:3.7-alpine - test-3.6: + - image: python:3.12-alpine + test-3.11: <<: *test-template docker: - - image: python:3.6-alpine - test-3.5: + - image: python:3.11-alpine + test-3.10: <<: *test-template docker: - - image: python:3.5-alpine - test-3.4: + - image: python:3.10-alpine + test-3.9: <<: *test-template docker: - - image: python:3.4-alpine + - image: python:3.9-alpine test-2.7: <<: *test-template docker: diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..7dd9a76 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: ["code-of-kpp", "eigenein"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d9d1c3..110b2b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### `2.2.1` + +* Fix: split message detection #182 #184 (@fpalamour) + ### `2.2.0` * New: allow customizing how error PDUs are handled (@davidshepherd7) diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..8fb6a51 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @eigenein @code-of-kpp diff --git a/setup.py b/setup.py index 905ca20..b12fc31 100644 --- a/setup.py +++ b/setup.py @@ -12,8 +12,8 @@ setup( name='smpplib', - version='2.2.0', - url='https://github.com/podshumok/python-smpplib', + version='2.2.4', + url='https://github.com/python-smpplib/python-smpplib', description='SMPP library for python', packages=find_packages(), install_requires=['six'], @@ -29,10 +29,11 @@ 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', 'Programming Language :: Python', 'Topic :: Communications :: Telephony', 'Topic :: Communications', diff --git a/smpplib/client.py b/smpplib/client.py index bb44b04..a335d15 100644 --- a/smpplib/client.py +++ b/smpplib/client.py @@ -92,7 +92,7 @@ def __init__( self.allow_unknown_opt_params = allow_unknown_opt_params - self._socket = self._create_socket() + self._socket = None def __enter__(self): return self @@ -207,39 +207,43 @@ def send_pdu(self, p): )) self.logger.debug('Sending %s PDU', p.command) - generated = p.generate() - self.logger.debug('>>%s (%d bytes)', binascii.b2a_hex(generated), len(generated)) - sent = 0 + try: + self._socket.sendall(generated) + except socket.error as e: + self.logger.warning(e) + raise exceptions.ConnectionError() + + return True - while sent < len(generated): + def _recv_exact(self, exact_size): + """ + Keep reading from self._socket until exact_size bytes have been read + """ + parts = [] + received = 0 + while received < exact_size: try: - sent_last = self._socket.send(generated[sent:]) + part = self._socket.recv(exact_size - received) + except socket.timeout: + raise except socket.error as e: self.logger.warning(e) raise exceptions.ConnectionError() - if sent_last == 0: + if not part: raise exceptions.ConnectionError() - sent += sent_last - - return True + received += len(part) + parts.append(part) + return b"".join(parts) def read_pdu(self): """Read PDU from the SMSC""" self.logger.debug('Waiting for PDU...') - try: - raw_len = self._socket.recv(4) - except socket.timeout: - raise - except socket.error as e: - self.logger.warning(e) - raise exceptions.ConnectionError() - if not raw_len: - raise exceptions.ConnectionError() + raw_len = self._recv_exact(4) try: length = struct.unpack('>L', raw_len)[0] @@ -247,18 +251,7 @@ def read_pdu(self): self.logger.warning('Receive broken pdu... %s', repr(raw_len)) raise exceptions.PDUError('Broken PDU') - raw_pdu = raw_len - while len(raw_pdu) < length: - try: - raw_pdu_part = self._socket.recv(length - len(raw_pdu)) - except socket.timeout: - raise - except socket.error as e: - self.logger.warning(e) - raise exceptions.ConnectionError() - if not raw_pdu: - raise exceptions.ConnectionError() - raw_pdu += raw_pdu_part + raw_pdu = raw_len + self._recv_exact(length - 4) self.logger.debug('<<%s (%d bytes)', binascii.b2a_hex(raw_pdu), len(raw_pdu)) diff --git a/smpplib/command.py b/smpplib/command.py index b19b0c6..bde5840 100644 --- a/smpplib/command.py +++ b/smpplib/command.py @@ -410,7 +410,7 @@ class BindTransmitter(Command): ) def __init__(self, command, **kwargs): - super(BindTransmitter, self).__init__(command, need_sequence=False, **kwargs) + super(BindTransmitter, self).__init__(command, **kwargs) self._set_vars(**(dict.fromkeys(self.params))) self.interface_version = consts.SMPP_VERSION_34 @@ -817,7 +817,7 @@ class DeliverSM(SubmitSM): ) def __init__(self, command, **kwargs): - super(DeliverSM, self).__init__(command, need_sequence=False, **kwargs) + super(DeliverSM, self).__init__(command, **kwargs) self._set_vars(**(dict.fromkeys(self.params))) @@ -900,7 +900,7 @@ class Unbind(Command): params_order = () def __init__(self, command, **kwargs): - super(Unbind, self).__init__(command, need_sequence=False, **kwargs) + super(Unbind, self).__init__(command, **kwargs) class UnbindResp(Command): @@ -919,7 +919,7 @@ class EnquireLink(Command): params_order = () def __init__(self, command, **kwargs): - super(EnquireLink, self).__init__(command, need_sequence=False, **kwargs) + super(EnquireLink, self).__init__(command, **kwargs) class EnquireLinkResp(Command): diff --git a/smpplib/consts.py b/smpplib/consts.py index 2987354..8218d93 100644 --- a/smpplib/consts.py +++ b/smpplib/consts.py @@ -6,7 +6,7 @@ # SMPP 3.4, 2.2.1.2 SEVENBIT_LENGTH = 160 EIGHTBIT_LENGTH = 140 -UCS2_LENGTH = 70 +UCS2_LENGTH = 140 MULTIPART_HEADER_SIZE = 6 diff --git a/smpplib/gsm.py b/smpplib/gsm.py index c3336a4..9d70f98 100644 --- a/smpplib/gsm.py +++ b/smpplib/gsm.py @@ -20,7 +20,7 @@ def make_parts(text, encoding=consts.SMPP_ENCODING_DEFAULT, use_udhi=True): encode, split_length, part_size = ENCODINGS[encoding] encoded_text = encode(text) - if len(text) > split_length: + if len(encoded_text) > split_length: if use_udhi: # Split the text into well-formed parts. esm_class = consts.SMPP_GSMFEAT_UDHI diff --git a/smpplib/tests/test_command.py b/smpplib/tests/test_command.py index a0e2433..0c47891 100644 --- a/smpplib/tests/test_command.py +++ b/smpplib/tests/test_command.py @@ -1,11 +1,13 @@ from smpplib import consts, exceptions +from smpplib.client import Client from smpplib.command import DeliverSM import pytest def test_parse_deliver_sm(): - pdu = DeliverSM('deliver_sm') + client = Client("localhost", 5679) + pdu = DeliverSM('deliver_sm', client=client) pdu.parse( b"\x00\x00\x00\xcb\x00\x00\x00\x05\x00\x00\x00\x00\x00\x00\x00\x01\x00" b"\x01\x0131600000000\x00\x05\x00XXX YYYY\x00\x04\x00\x00\x00\x00\x00" @@ -26,7 +28,8 @@ def test_parse_deliver_sm(): def test_unrecognised_optional_parameters(): - pdu = DeliverSM("deliver_sm", allow_unknown_opt_params=True) + client = Client("localhost", 5679) + pdu = DeliverSM("deliver_sm", client=client, allow_unknown_opt_params=True) pdu.parse(b'\x00\x00\x00\xa8\x00\x00\x00\x05\x00\x00\x00\x00/p\xc6' b'\x9a\x00\x00\x0022549909028\x00\x01\x00\x00\x04\x00\x00' b'\x00\x00\x00\x00\x00\x00iid:795920026 sub:001 dlvrd:001 ' @@ -37,7 +40,7 @@ def test_unrecognised_optional_parameters(): # This is only to avoid a breaking change, at some point the other behaviour # should become the default. with pytest.raises(exceptions.UnknownCommandError): - pdu2 = DeliverSM("deliver_sm") + pdu2 = DeliverSM("deliver_sm", client=client) pdu2.parse(b'\x00\x00\x00\xa8\x00\x00\x00\x05\x00\x00\x00\x00/p\xc6' b'\x9a\x00\x00\x0022549909028\x00\x01\x00\x00\x04\x00\x00' b'\x00\x00\x00\x00\x00\x00iid:795920026 sub:001 dlvrd:001 '