Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 1 | # osmo_gsm_tester: SMPP ESME to talk to SMSC |
| 2 | # |
| 3 | # Copyright (C) 2017 by sysmocom - s.f.m.c. GmbH |
| 4 | # |
| 5 | # Author: Pau Espin Pedrol <pespin@sysmocom.de> |
| 6 | # |
| 7 | # This program is free software: you can redistribute it and/or modify |
| 8 | # it under the terms of the GNU Affero General Public License as |
| 9 | # published by the Free Software Foundation, either version 3 of the |
| 10 | # License, or (at your option) any later version. |
| 11 | # |
| 12 | # This program is distributed in the hope that it will be useful, |
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | # GNU Affero General Public License for more details. |
| 16 | # |
| 17 | # You should have received a copy of the GNU Affero General Public License |
| 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 | |
Pau Espin Pedrol | e1a58bd | 2020-04-10 20:46:07 +0200 | [diff] [blame] | 20 | from ..core import log |
| 21 | from ..core.event_loop import MainLoop |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 22 | |
| 23 | # if you want to know what's happening inside python-smpplib |
| 24 | #import logging |
| 25 | #logging.basicConfig(level='DEBUG') |
| 26 | |
| 27 | MAX_SYS_ID_LEN = 16 |
| 28 | MAX_PASSWD_LEN = 16 |
| 29 | |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 30 | smpplib_gsm = None |
| 31 | smpplib_client = None |
| 32 | smpplib_command = None |
| 33 | smpplib_consts = None |
| 34 | smpplib_exceptions = None |
| 35 | def _import_smpplib_modules(): |
| 36 | global smpplib_gsm, smpplib_client, smpplib_command, smpplib_consts, smpplib_exceptions |
| 37 | if smpplib_exceptions is None: |
| 38 | import smpplib.gsm |
| 39 | import smpplib.client |
| 40 | import smpplib.command |
| 41 | import smpplib.consts |
| 42 | import smpplib.exceptions |
| 43 | smpplib_gsm = smpplib.gsm |
| 44 | smpplib_client = smpplib.client |
| 45 | smpplib_command = smpplib.command |
| 46 | smpplib_consts = smpplib.consts |
| 47 | smpplib_exceptions = smpplib.exceptions |
| 48 | |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 49 | class Esme(log.Origin): |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 50 | |
| 51 | def __init__(self, msisdn): |
| 52 | self.msisdn = msisdn |
| 53 | # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator. |
| 54 | self.set_system_id('esme-' + self.msisdn[-11:]) |
| 55 | super().__init__(log.C_TST, self.system_id) |
Pau Espin Pedrol | 5860367 | 2018-08-09 13:45:55 +0200 | [diff] [blame] | 56 | self.client = None |
| 57 | self.smsc = None |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 58 | self.set_password('esme-pwd') |
| 59 | self.connected = False |
| 60 | self.bound = False |
| 61 | self.listening = False |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 62 | self.references_pending_receipt = [] |
| 63 | self.next_user_message_reference = 1 |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 64 | _import_smpplib_modules() |
| 65 | self.MSGMODE_TRANSACTION = smpplib_consts.SMPP_MSGMODE_FORWARD |
| 66 | self.MSGMODE_STOREFORWARD = smpplib_consts.SMPP_MSGMODE_STOREFORWARD |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 67 | |
| 68 | def __del__(self): |
Pau Espin Pedrol | ac9c1bb | 2017-08-10 10:59:40 +0200 | [diff] [blame] | 69 | self.cleanup() |
| 70 | |
| 71 | def cleanup(self): |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 72 | try: |
| 73 | self.disconnect() |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 74 | except smpplib_exceptions.ConnectionError: |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 75 | pass |
| 76 | |
| 77 | def set_smsc(self, smsc): |
| 78 | self.smsc = smsc |
| 79 | |
| 80 | def set_system_id(self, name): |
| 81 | if len(name) > MAX_SYS_ID_LEN: |
| 82 | raise log.Error('Esme system_id too long! %d vs %d', len(name), MAX_SYS_ID_LEN) |
| 83 | self.system_id = name |
| 84 | |
| 85 | def set_password(self, password): |
| 86 | if len(password) > MAX_PASSWD_LEN: |
| 87 | raise log.Error('Esme password too long! %d vs %d', len(password), MAX_PASSWD_LEN) |
| 88 | self.password = password |
| 89 | |
| 90 | def conf_for_smsc(self): |
| 91 | config = { 'system_id': self.system_id, 'password': self.password } |
| 92 | return config |
| 93 | |
| 94 | def poll(self): |
| 95 | self.client.poll() |
| 96 | |
| 97 | def start_listening(self): |
| 98 | self.listening = True |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 99 | MainLoop.register_poll_func(self.poll) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 100 | |
| 101 | def stop_listening(self): |
| 102 | if not self.listening: |
| 103 | return |
| 104 | self.listening = False |
| 105 | # Empty the queue before processing the unbind + disconnect PDUs |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 106 | MainLoop.unregister_poll_func(self.poll) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 107 | self.poll() |
| 108 | |
| 109 | def connect(self): |
| 110 | host, port = self.smsc.addr_port |
| 111 | if self.client: |
| 112 | self.disconnect() |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 113 | self.client = smpplib_client.Client(host, port, timeout=None) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 114 | self.client.set_message_sent_handler( |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 115 | lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) ) |
| 116 | self.client.set_message_received_handler(self._message_received_handler) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 117 | self.client.connect() |
| 118 | self.connected = True |
| 119 | self.client.bind_transceiver(system_id=self.system_id, password=self.password) |
| 120 | self.bound = True |
Pau Espin Pedrol | 522ae68 | 2017-07-31 17:25:15 +0200 | [diff] [blame] | 121 | self.log('Connected and bound successfully to %s (%s:%d). Starting to listen.' % (self.system_id, host, port)) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 122 | self.start_listening() |
| 123 | |
| 124 | def disconnect(self): |
| 125 | self.stop_listening() |
| 126 | if self.bound: |
| 127 | self.client.unbind() |
| 128 | self.bound = False |
| 129 | if self.connected: |
| 130 | self.client.disconnect() |
| 131 | self.connected = False |
| 132 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 133 | def _message_received_handler(self, pdu, *args): |
| 134 | self.dbg('message received:', seq=pdu.sequence) |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 135 | if isinstance(pdu, smpplib_command.AlertNotification): |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 136 | self.dbg('message received: AlertNotification:', ms_availability_status=pdu.ms_availability_status) |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 137 | elif isinstance(pdu, smpplib_command.DeliverSM): |
Pau Espin Pedrol | 69373b3 | 2017-08-17 17:01:50 +0200 | [diff] [blame] | 138 | umref = int(pdu.user_message_reference) |
| 139 | self.dbg('message received: DeliverSM', references_pending_receipt=self.references_pending_receipt, user_message_reference=umref) |
| 140 | self.references_pending_receipt.remove(umref) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 141 | |
| 142 | def receipt_was_received(self, umref): |
Pau Espin Pedrol | 3169ffb | 2017-08-18 17:22:19 +0200 | [diff] [blame] | 143 | return umref not in self.references_pending_receipt |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 144 | |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 145 | def run_method_expect_failure(self, errcode, method, *args): |
| 146 | try: |
| 147 | method(*args) |
| 148 | #it should not succeed, raise an exception: |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 149 | raise log.Error('SMPP Failure: %s should have failed with SMPP error %d (%s) but succeeded.' % (method, errcode, smpplib_consts.DESCRIPTIONS[errcode])) |
| 150 | except smpplib_exceptions.PDUError as e: |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 151 | if e.args[1] != errcode: |
| 152 | raise e |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 153 | self.dbg('Expected failure triggered: %d' % errcode) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 154 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 155 | def sms_send(self, sms_obj, mode, receipt=False): |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 156 | parts, encoding_flag, msg_type_flag = smpplib_gsm.make_parts(str(sms_obj)) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 157 | seqs = [] |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 158 | self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn())) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 159 | umref = self.next_user_message_reference |
Pau Espin Pedrol | 5adb5c7 | 2017-08-17 17:57:58 +0200 | [diff] [blame] | 160 | self.next_user_message_reference = (self.next_user_message_reference + 1) % (1 << 8) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 161 | for part in parts: |
| 162 | pdu = self.client.send_message( |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 163 | source_addr_ton=smpplib_consts.SMPP_TON_INTL, |
| 164 | source_addr_npi=smpplib_consts.SMPP_NPI_ISDN, |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 165 | source_addr=sms_obj.src_msisdn(), |
Pau Espin Pedrol | b1d8d30 | 2020-05-11 11:55:42 +0200 | [diff] [blame] | 166 | dest_addr_ton=smpplib_consts.SMPP_TON_INTL, |
| 167 | dest_addr_npi=smpplib_consts.SMPP_NPI_ISDN, |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 168 | destination_addr=sms_obj.dst_msisdn(), |
| 169 | short_message=part, |
| 170 | data_coding=encoding_flag, |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 171 | esm_class=mode, |
| 172 | registered_delivery=receipt, |
| 173 | user_message_reference=umref, |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 174 | ) |
| 175 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 176 | self.dbg('sent part with seq', pdu.sequence) |
| 177 | seqs.append(pdu.sequence) |
| 178 | if receipt: |
| 179 | self.references_pending_receipt.append(umref) |
| 180 | return umref, seqs |
| 181 | |
| 182 | def _process_pdus_pending(self, pdu, **kwargs): |
| 183 | self.dbg('message sent resp with seq', pdu.sequence, ', pdus_pending:', self.pdus_pending) |
Pau Espin Pedrol | 91c75b3 | 2017-07-20 22:41:22 +0200 | [diff] [blame] | 184 | if pdu.sequence in self.pdus_pending: |
| 185 | self.pdus_pending.remove(pdu.sequence) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 186 | |
| 187 | def sms_send_wait_resp(self, sms_obj, mode, receipt=False): |
| 188 | old_func = self.client.message_sent_handler |
| 189 | try: |
| 190 | umref, self.pdus_pending = self.sms_send(sms_obj, mode, receipt) |
| 191 | self.dbg('pdus_pending:', self.pdus_pending) |
| 192 | self.client.set_message_sent_handler(self._process_pdus_pending) |
Pau Espin Pedrol | 664e383 | 2020-06-10 19:30:33 +0200 | [diff] [blame] | 193 | MainLoop.wait(lambda: len(self.pdus_pending) == 0, timeout=10) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 194 | return umref |
| 195 | finally: |
| 196 | self.client.set_message_sent_handler(old_func) |
| 197 | |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 198 | # vim: expandtab tabstop=4 shiftwidth=4 |