diff --git a/vumi/transports/smpp/clientserver/client.py b/vumi/transports/smpp/clientserver/client.py index 1dd44f33e..8fe7dd432 100644 --- a/vumi/transports/smpp/clientserver/client.py +++ b/vumi/transports/smpp/clientserver/client.py @@ -2,6 +2,7 @@ import json import uuid +from random import randint from twisted.internet import reactor from twisted.internet.protocol import Protocol, ClientFactory @@ -457,23 +458,56 @@ def submit_sm(self, **kwargs): 'dropping message: %s' % (self.state, kwargs))) returnValue(0) - sequence_number = yield self.get_next_seq() + sar_params = kwargs.pop('sar_params', None) + pdu_params = self.bind_params.copy() pdu_params.update(kwargs) + message = pdu_params['short_message'] + + if self.config.send_multipart_sar and sar_params is None: + if len(message) > 130: + sequence_number = yield self._submit_multipart_sar( + **pdu_params) + returnValue(sequence_number) + + sequence_number = yield self.get_next_seq() pdu = SubmitSM(sequence_number, **pdu_params) if kwargs.get('message_type', 'sms') == 'ussd': update_ussd_pdu(pdu, kwargs.get('continue_session', True), kwargs.get('session_info', None)) - message = pdu_params['short_message'] if self.config.send_long_messages and len(message) > 254: pdu.add_message_payload(''.join('%02x' % ord(c) for c in message)) + if sar_params: + pdu.set_sar_msg_ref_num(sar_params['msg_ref_num']) + pdu.set_sar_total_segments(sar_params['total_segments']) + pdu.set_sar_segment_seqnum(sar_params['segment_seqnum']) + self.send_pdu(pdu) yield self.push_unacked(sequence_number) returnValue(sequence_number) + @inlineCallbacks + def _submit_multipart_sar(self, **pdu_params): + message = pdu_params['short_message'] + split_msg = [] + while message: + split_msg.append(message[:130]) + message = message[130:] + ref_num = randint(1, 255) + for i, msg in enumerate(split_msg): + params = pdu_params.copy() + params['short_message'] = msg + params['sar_params'] = { + 'msg_ref_num': ref_num, + 'total_segments': len(split_msg), + 'segment_seqnum': i + 1, + } + sequence_number = yield self.submit_sm(**params) + returnValue(sequence_number) + @inlineCallbacks def enquire_link(self, **kwargs): if self.state in ['BOUND_TX', 'BOUND_RX', 'BOUND_TRX']: diff --git a/vumi/transports/smpp/clientserver/tests/test_client.py b/vumi/transports/smpp/clientserver/tests/test_client.py index f7b5053f2..d3e28194b 100644 --- a/vumi/transports/smpp/clientserver/tests/test_client.py +++ b/vumi/transports/smpp/clientserver/tests/test_client.py @@ -211,6 +211,32 @@ def test_submit_sm_sms_long(self): self.assertEqual(''.join('%02x' % ord(c) for c in long_message), pdu_opts['message_payload']) + @inlineCallbacks + def test_submit_sm_sms_multipart_sar(self): + """Submit a long SMS message using multipart sar fields.""" + esme = yield self.get_esme(config={ + 'send_multipart_sar': True, + }) + long_message = 'This is a long message.' * 20 + yield esme.submit_sm(short_message=long_message) + self.assertEqual(4, len(esme.fake_sent_pdus)) + msg_parts = [] + msg_refs = [] + + for i, sm_pdu in enumerate(esme.fake_sent_pdus): + sm = unpack_pdu(sm_pdu.get_bin()) + pdu_opts = unpacked_pdu_opts(sm) + mandatory_parameters = sm['body']['mandatory_parameters'] + + self.assertEqual('submit_sm', sm['header']['command_id']) + msg_parts.append(mandatory_parameters['short_message']) + msg_refs.append(pdu_opts['sar_msg_ref_num']) + self.assertEqual(i + 1, pdu_opts['sar_segment_seqnum']) + self.assertEqual(4, pdu_opts['sar_total_segments']) + + self.assertEqual(long_message, ''.join(msg_parts)) + self.assertEqual(1, len(set(msg_refs))) + @inlineCallbacks def test_submit_sm_ussd_continue(self): """Submit a USSD message with a session continue flag.""" diff --git a/vumi/transports/smpp/transport.py b/vumi/transports/smpp/transport.py index c05a0ec8c..a0403123c 100644 --- a/vumi/transports/smpp/transport.py +++ b/vumi/transports/smpp/transport.py @@ -97,6 +97,10 @@ class SmppTransportConfig(Transport.CONFIG_CLASS): "`message_payload` optional field instead of the `short_message` " "field. Default is `False`, simply because that maintains previous " "behaviour.", default=False, static=True) + send_multipart_sar = ConfigBool( + "If `True`, messages longer than 140 bytes will be sent as a series " + "of smaller messages with the sar_* parameters set. Default is " + "`False`.", default=False, static=True) split_bind_prefix = ConfigText( "This is the Redis prefix to use for storing things like sequence " "numbers and message ids for delivery report handling. It defaults "