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 | |
| 20 | import smpplib.gsm |
| 21 | import smpplib.client |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 22 | import smpplib.command |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 23 | import smpplib.consts |
| 24 | import smpplib.exceptions |
| 25 | |
Holger Hans Peter Freyther | d03acdf | 2018-09-23 18:06:48 +0100 | [diff] [blame] | 26 | from . import log |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 27 | from .event_loop import MainLoop |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 28 | |
| 29 | # if you want to know what's happening inside python-smpplib |
| 30 | #import logging |
| 31 | #logging.basicConfig(level='DEBUG') |
| 32 | |
| 33 | MAX_SYS_ID_LEN = 16 |
| 34 | MAX_PASSWD_LEN = 16 |
| 35 | |
| 36 | class Esme(log.Origin): |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 37 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 38 | MSGMODE_TRANSACTION = smpplib.consts.SMPP_MSGMODE_FORWARD |
| 39 | MSGMODE_STOREFORWARD = smpplib.consts.SMPP_MSGMODE_STOREFORWARD |
| 40 | |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 41 | def __init__(self, msisdn): |
| 42 | self.msisdn = msisdn |
| 43 | # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator. |
| 44 | self.set_system_id('esme-' + self.msisdn[-11:]) |
| 45 | super().__init__(log.C_TST, self.system_id) |
Pau Espin Pedrol | 5860367 | 2018-08-09 13:45:55 +0200 | [diff] [blame] | 46 | self.client = None |
| 47 | self.smsc = None |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 48 | self.set_password('esme-pwd') |
| 49 | self.connected = False |
| 50 | self.bound = False |
| 51 | self.listening = False |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 52 | self.references_pending_receipt = [] |
| 53 | self.next_user_message_reference = 1 |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 54 | |
| 55 | def __del__(self): |
Pau Espin Pedrol | ac9c1bb | 2017-08-10 10:59:40 +0200 | [diff] [blame] | 56 | self.cleanup() |
| 57 | |
| 58 | def cleanup(self): |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 59 | try: |
| 60 | self.disconnect() |
| 61 | except smpplib.exceptions.ConnectionError: |
| 62 | pass |
| 63 | |
| 64 | def set_smsc(self, smsc): |
| 65 | self.smsc = smsc |
| 66 | |
| 67 | def set_system_id(self, name): |
| 68 | if len(name) > MAX_SYS_ID_LEN: |
| 69 | raise log.Error('Esme system_id too long! %d vs %d', len(name), MAX_SYS_ID_LEN) |
| 70 | self.system_id = name |
| 71 | |
| 72 | def set_password(self, password): |
| 73 | if len(password) > MAX_PASSWD_LEN: |
| 74 | raise log.Error('Esme password too long! %d vs %d', len(password), MAX_PASSWD_LEN) |
| 75 | self.password = password |
| 76 | |
| 77 | def conf_for_smsc(self): |
| 78 | config = { 'system_id': self.system_id, 'password': self.password } |
| 79 | return config |
| 80 | |
| 81 | def poll(self): |
| 82 | self.client.poll() |
| 83 | |
| 84 | def start_listening(self): |
| 85 | self.listening = True |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 86 | MainLoop.register_poll_func(self.poll) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 87 | |
| 88 | def stop_listening(self): |
| 89 | if not self.listening: |
| 90 | return |
| 91 | self.listening = False |
| 92 | # Empty the queue before processing the unbind + disconnect PDUs |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 93 | MainLoop.unregister_poll_func(self.poll) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 94 | self.poll() |
| 95 | |
| 96 | def connect(self): |
| 97 | host, port = self.smsc.addr_port |
| 98 | if self.client: |
| 99 | self.disconnect() |
| 100 | self.client = smpplib.client.Client(host, port, timeout=None) |
| 101 | self.client.set_message_sent_handler( |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 102 | lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) ) |
| 103 | self.client.set_message_received_handler(self._message_received_handler) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 104 | self.client.connect() |
| 105 | self.connected = True |
| 106 | self.client.bind_transceiver(system_id=self.system_id, password=self.password) |
| 107 | self.bound = True |
Pau Espin Pedrol | 522ae68 | 2017-07-31 17:25:15 +0200 | [diff] [blame] | 108 | 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] | 109 | self.start_listening() |
| 110 | |
| 111 | def disconnect(self): |
| 112 | self.stop_listening() |
| 113 | if self.bound: |
| 114 | self.client.unbind() |
| 115 | self.bound = False |
| 116 | if self.connected: |
| 117 | self.client.disconnect() |
| 118 | self.connected = False |
| 119 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 120 | def _message_received_handler(self, pdu, *args): |
| 121 | self.dbg('message received:', seq=pdu.sequence) |
| 122 | if isinstance(pdu, smpplib.command.AlertNotification): |
| 123 | self.dbg('message received: AlertNotification:', ms_availability_status=pdu.ms_availability_status) |
| 124 | elif isinstance(pdu, smpplib.command.DeliverSM): |
Pau Espin Pedrol | 69373b3 | 2017-08-17 17:01:50 +0200 | [diff] [blame] | 125 | umref = int(pdu.user_message_reference) |
| 126 | self.dbg('message received: DeliverSM', references_pending_receipt=self.references_pending_receipt, user_message_reference=umref) |
| 127 | self.references_pending_receipt.remove(umref) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 128 | |
| 129 | def receipt_was_received(self, umref): |
Pau Espin Pedrol | 3169ffb | 2017-08-18 17:22:19 +0200 | [diff] [blame] | 130 | return umref not in self.references_pending_receipt |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 131 | |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 132 | def run_method_expect_failure(self, errcode, method, *args): |
| 133 | try: |
| 134 | method(*args) |
| 135 | #it should not succeed, raise an exception: |
| 136 | raise log.Error('SMPP Failure: %s should have failed with SMPP error %d (%s) but succeeded.' % (method, errcode, smpplib.consts.DESCRIPTIONS[errcode])) |
| 137 | except smpplib.exceptions.PDUError as e: |
| 138 | if e.args[1] != errcode: |
| 139 | raise e |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 140 | self.dbg('Expected failure triggered: %d' % errcode) |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 141 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 142 | def sms_send(self, sms_obj, mode, receipt=False): |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 143 | 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] | 144 | seqs = [] |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 145 | 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] | 146 | umref = self.next_user_message_reference |
Pau Espin Pedrol | 5adb5c7 | 2017-08-17 17:57:58 +0200 | [diff] [blame] | 147 | 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] | 148 | for part in parts: |
| 149 | pdu = self.client.send_message( |
| 150 | source_addr_ton=smpplib.consts.SMPP_TON_INTL, |
| 151 | source_addr_npi=smpplib.consts.SMPP_NPI_ISDN, |
| 152 | source_addr=sms_obj.src_msisdn(), |
| 153 | dest_addr_ton=smpplib.consts.SMPP_TON_INTL, |
| 154 | dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN, |
| 155 | destination_addr=sms_obj.dst_msisdn(), |
| 156 | short_message=part, |
| 157 | data_coding=encoding_flag, |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 158 | esm_class=mode, |
| 159 | registered_delivery=receipt, |
| 160 | user_message_reference=umref, |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 161 | ) |
| 162 | |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 163 | self.dbg('sent part with seq', pdu.sequence) |
| 164 | seqs.append(pdu.sequence) |
| 165 | if receipt: |
| 166 | self.references_pending_receipt.append(umref) |
| 167 | return umref, seqs |
| 168 | |
| 169 | def _process_pdus_pending(self, pdu, **kwargs): |
| 170 | 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] | 171 | if pdu.sequence in self.pdus_pending: |
| 172 | self.pdus_pending.remove(pdu.sequence) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 173 | |
| 174 | def sms_send_wait_resp(self, sms_obj, mode, receipt=False): |
| 175 | old_func = self.client.message_sent_handler |
| 176 | try: |
| 177 | umref, self.pdus_pending = self.sms_send(sms_obj, mode, receipt) |
| 178 | self.dbg('pdus_pending:', self.pdus_pending) |
| 179 | self.client.set_message_sent_handler(self._process_pdus_pending) |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 180 | MainLoop.wait(self, lambda: len(self.pdus_pending) == 0, timeout=10) |
Pau Espin Pedrol | db0d8ab | 2017-07-05 13:38:33 +0200 | [diff] [blame] | 181 | return umref |
| 182 | finally: |
| 183 | self.client.set_message_sent_handler(old_func) |
| 184 | |
Pau Espin Pedrol | 2d16f6f | 2017-05-30 15:33:57 +0200 | [diff] [blame] | 185 | # vim: expandtab tabstop=4 shiftwidth=4 |