blob: cd416abfe19626983b9b436f2b731dfc1942e087 [file] [log] [blame]
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +02001# 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 Pedrole1a58bd2020-04-10 20:46:07 +020020from ..core import log
21from ..core.event_loop import MainLoop
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020022
23# if you want to know what's happening inside python-smpplib
24#import logging
25#logging.basicConfig(level='DEBUG')
26
27MAX_SYS_ID_LEN = 16
28MAX_PASSWD_LEN = 16
29
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +020030smpplib_gsm = None
31smpplib_client = None
32smpplib_command = None
33smpplib_consts = None
34smpplib_exceptions = None
35def _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 Pedrol2d16f6f2017-05-30 15:33:57 +020049class Esme(log.Origin):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020050
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 Pedrol58603672018-08-09 13:45:55 +020056 self.client = None
57 self.smsc = None
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020058 self.set_password('esme-pwd')
59 self.connected = False
60 self.bound = False
61 self.listening = False
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020062 self.references_pending_receipt = []
63 self.next_user_message_reference = 1
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +020064 _import_smpplib_modules()
65 self.MSGMODE_TRANSACTION = smpplib_consts.SMPP_MSGMODE_FORWARD
66 self.MSGMODE_STOREFORWARD = smpplib_consts.SMPP_MSGMODE_STOREFORWARD
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020067
68 def __del__(self):
Pau Espin Pedrolac9c1bb2017-08-10 10:59:40 +020069 self.cleanup()
70
71 def cleanup(self):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020072 try:
73 self.disconnect()
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +020074 except smpplib_exceptions.ConnectionError:
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020075 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 Pedrol9a4631c2018-03-28 19:17:34 +020099 MainLoop.register_poll_func(self.poll)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200100
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 Pedrol9a4631c2018-03-28 19:17:34 +0200106 MainLoop.unregister_poll_func(self.poll)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200107 self.poll()
108
109 def connect(self):
110 host, port = self.smsc.addr_port
111 if self.client:
112 self.disconnect()
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200113 self.client = smpplib_client.Client(host, port, timeout=None)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200114 self.client.set_message_sent_handler(
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200115 lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) )
116 self.client.set_message_received_handler(self._message_received_handler)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200117 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 Pedrol522ae682017-07-31 17:25:15 +0200121 self.log('Connected and bound successfully to %s (%s:%d). Starting to listen.' % (self.system_id, host, port))
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200122 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200133 def _message_received_handler(self, pdu, *args):
134 self.dbg('message received:', seq=pdu.sequence)
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200135 if isinstance(pdu, smpplib_command.AlertNotification):
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200136 self.dbg('message received: AlertNotification:', ms_availability_status=pdu.ms_availability_status)
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200137 elif isinstance(pdu, smpplib_command.DeliverSM):
Pau Espin Pedrol69373b32017-08-17 17:01:50 +0200138 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200141
142 def receipt_was_received(self, umref):
Pau Espin Pedrol3169ffb2017-08-18 17:22:19 +0200143 return umref not in self.references_pending_receipt
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200144
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200145 def run_method_expect_failure(self, errcode, method, *args):
146 try:
147 method(*args)
148 #it should not succeed, raise an exception:
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200149 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 Pedrol2d16f6f2017-05-30 15:33:57 +0200151 if e.args[1] != errcode:
152 raise e
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200153 self.dbg('Expected failure triggered: %d' % errcode)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200154
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200155 def sms_send(self, sms_obj, mode, receipt=False):
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200156 parts, encoding_flag, msg_type_flag = smpplib_gsm.make_parts(str(sms_obj))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200157 seqs = []
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200158 self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn()))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200159 umref = self.next_user_message_reference
Pau Espin Pedrol5adb5c72017-08-17 17:57:58 +0200160 self.next_user_message_reference = (self.next_user_message_reference + 1) % (1 << 8)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200161 for part in parts:
162 pdu = self.client.send_message(
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200163 source_addr_ton=smpplib_consts.SMPP_TON_INTL,
164 source_addr_npi=smpplib_consts.SMPP_NPI_ISDN,
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200165 source_addr=sms_obj.src_msisdn(),
Pau Espin Pedrolb1d8d302020-05-11 11:55:42 +0200166 dest_addr_ton=smpplib_consts.SMPP_TON_INTL,
167 dest_addr_npi=smpplib_consts.SMPP_NPI_ISDN,
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200168 destination_addr=sms_obj.dst_msisdn(),
169 short_message=part,
170 data_coding=encoding_flag,
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200171 esm_class=mode,
172 registered_delivery=receipt,
173 user_message_reference=umref,
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200174 )
175
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200176 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 Pedrol91c75b32017-07-20 22:41:22 +0200184 if pdu.sequence in self.pdus_pending:
185 self.pdus_pending.remove(pdu.sequence)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200186
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 Pedrol664e3832020-06-10 19:30:33 +0200193 MainLoop.wait(lambda: len(self.pdus_pending) == 0, timeout=10)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200194 return umref
195 finally:
196 self.client.set_message_sent_handler(old_func)
197
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200198# vim: expandtab tabstop=4 shiftwidth=4