blob: e23e88c976d1facb79b58c508e40c70fa11194b4 [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
30class Esme(log.Origin):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020031
32 def __init__(self, msisdn):
33 self.msisdn = msisdn
34 # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator.
35 self.set_system_id('esme-' + self.msisdn[-11:])
36 super().__init__(log.C_TST, self.system_id)
Pau Espin Pedrol58603672018-08-09 13:45:55 +020037 self.client = None
38 self.smsc = None
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020039 self.set_password('esme-pwd')
40 self.connected = False
41 self.bound = False
42 self.listening = False
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020043 self.references_pending_receipt = []
44 self.next_user_message_reference = 1
Pau Espin Pedrol45149392020-05-05 17:03:54 +020045 import smpplib.gsm
46 import smpplib.client
47 import smpplib.command
48 import smpplib.consts
49 import smpplib.exceptions
50 self.MSGMODE_TRANSACTION = smpplib.consts.SMPP_MSGMODE_FORWARD
51 self.MSGMODE_STOREFORWARD = smpplib.consts.SMPP_MSGMODE_STOREFORWARD
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020052
53 def __del__(self):
Pau Espin Pedrolac9c1bb2017-08-10 10:59:40 +020054 self.cleanup()
55
56 def cleanup(self):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020057 try:
58 self.disconnect()
59 except smpplib.exceptions.ConnectionError:
60 pass
61
62 def set_smsc(self, smsc):
63 self.smsc = smsc
64
65 def set_system_id(self, name):
66 if len(name) > MAX_SYS_ID_LEN:
67 raise log.Error('Esme system_id too long! %d vs %d', len(name), MAX_SYS_ID_LEN)
68 self.system_id = name
69
70 def set_password(self, password):
71 if len(password) > MAX_PASSWD_LEN:
72 raise log.Error('Esme password too long! %d vs %d', len(password), MAX_PASSWD_LEN)
73 self.password = password
74
75 def conf_for_smsc(self):
76 config = { 'system_id': self.system_id, 'password': self.password }
77 return config
78
79 def poll(self):
80 self.client.poll()
81
82 def start_listening(self):
83 self.listening = True
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +020084 MainLoop.register_poll_func(self.poll)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020085
86 def stop_listening(self):
87 if not self.listening:
88 return
89 self.listening = False
90 # Empty the queue before processing the unbind + disconnect PDUs
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +020091 MainLoop.unregister_poll_func(self.poll)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020092 self.poll()
93
94 def connect(self):
95 host, port = self.smsc.addr_port
96 if self.client:
97 self.disconnect()
98 self.client = smpplib.client.Client(host, port, timeout=None)
99 self.client.set_message_sent_handler(
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200100 lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) )
101 self.client.set_message_received_handler(self._message_received_handler)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200102 self.client.connect()
103 self.connected = True
104 self.client.bind_transceiver(system_id=self.system_id, password=self.password)
105 self.bound = True
Pau Espin Pedrol522ae682017-07-31 17:25:15 +0200106 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 +0200107 self.start_listening()
108
109 def disconnect(self):
110 self.stop_listening()
111 if self.bound:
112 self.client.unbind()
113 self.bound = False
114 if self.connected:
115 self.client.disconnect()
116 self.connected = False
117
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200118 def _message_received_handler(self, pdu, *args):
119 self.dbg('message received:', seq=pdu.sequence)
120 if isinstance(pdu, smpplib.command.AlertNotification):
121 self.dbg('message received: AlertNotification:', ms_availability_status=pdu.ms_availability_status)
122 elif isinstance(pdu, smpplib.command.DeliverSM):
Pau Espin Pedrol69373b32017-08-17 17:01:50 +0200123 umref = int(pdu.user_message_reference)
124 self.dbg('message received: DeliverSM', references_pending_receipt=self.references_pending_receipt, user_message_reference=umref)
125 self.references_pending_receipt.remove(umref)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200126
127 def receipt_was_received(self, umref):
Pau Espin Pedrol3169ffb2017-08-18 17:22:19 +0200128 return umref not in self.references_pending_receipt
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200129
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200130 def run_method_expect_failure(self, errcode, method, *args):
131 try:
132 method(*args)
133 #it should not succeed, raise an exception:
134 raise log.Error('SMPP Failure: %s should have failed with SMPP error %d (%s) but succeeded.' % (method, errcode, smpplib.consts.DESCRIPTIONS[errcode]))
135 except smpplib.exceptions.PDUError as e:
136 if e.args[1] != errcode:
137 raise e
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200138 self.dbg('Expected failure triggered: %d' % errcode)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200139
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200140 def sms_send(self, sms_obj, mode, receipt=False):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200141 parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(str(sms_obj))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200142 seqs = []
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200143 self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn()))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200144 umref = self.next_user_message_reference
Pau Espin Pedrol5adb5c72017-08-17 17:57:58 +0200145 self.next_user_message_reference = (self.next_user_message_reference + 1) % (1 << 8)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200146 for part in parts:
147 pdu = self.client.send_message(
148 source_addr_ton=smpplib.consts.SMPP_TON_INTL,
149 source_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
150 source_addr=sms_obj.src_msisdn(),
151 dest_addr_ton=smpplib.consts.SMPP_TON_INTL,
152 dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
153 destination_addr=sms_obj.dst_msisdn(),
154 short_message=part,
155 data_coding=encoding_flag,
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200156 esm_class=mode,
157 registered_delivery=receipt,
158 user_message_reference=umref,
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200159 )
160
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200161 self.dbg('sent part with seq', pdu.sequence)
162 seqs.append(pdu.sequence)
163 if receipt:
164 self.references_pending_receipt.append(umref)
165 return umref, seqs
166
167 def _process_pdus_pending(self, pdu, **kwargs):
168 self.dbg('message sent resp with seq', pdu.sequence, ', pdus_pending:', self.pdus_pending)
Pau Espin Pedrol91c75b32017-07-20 22:41:22 +0200169 if pdu.sequence in self.pdus_pending:
170 self.pdus_pending.remove(pdu.sequence)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200171
172 def sms_send_wait_resp(self, sms_obj, mode, receipt=False):
173 old_func = self.client.message_sent_handler
174 try:
175 umref, self.pdus_pending = self.sms_send(sms_obj, mode, receipt)
176 self.dbg('pdus_pending:', self.pdus_pending)
177 self.client.set_message_sent_handler(self._process_pdus_pending)
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +0200178 MainLoop.wait(self, lambda: len(self.pdus_pending) == 0, timeout=10)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200179 return umref
180 finally:
181 self.client.set_message_sent_handler(old_func)
182
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200183# vim: expandtab tabstop=4 shiftwidth=4