blob: 78421f6c13eade912d1b9f7efd8e65ebfd0c9f11 [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
20import smpplib.gsm
21import smpplib.client
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020022import smpplib.command
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020023import smpplib.consts
24import smpplib.exceptions
25
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +020026from . import log, util, sms
27from .event_loop import MainLoop
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020028
29# if you want to know what's happening inside python-smpplib
30#import logging
31#logging.basicConfig(level='DEBUG')
32
33MAX_SYS_ID_LEN = 16
34MAX_PASSWD_LEN = 16
35
36class Esme(log.Origin):
37 client = None
38 smsc = None
39
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020040 MSGMODE_TRANSACTION = smpplib.consts.SMPP_MSGMODE_FORWARD
41 MSGMODE_STOREFORWARD = smpplib.consts.SMPP_MSGMODE_STOREFORWARD
42
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020043 def __init__(self, msisdn):
44 self.msisdn = msisdn
45 # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator.
46 self.set_system_id('esme-' + self.msisdn[-11:])
47 super().__init__(log.C_TST, self.system_id)
48 self.set_password('esme-pwd')
49 self.connected = False
50 self.bound = False
51 self.listening = False
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020052 self.references_pending_receipt = []
53 self.next_user_message_reference = 1
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020054
55 def __del__(self):
Pau Espin Pedrolac9c1bb2017-08-10 10:59:40 +020056 self.cleanup()
57
58 def cleanup(self):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020059 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 Pedrol9a4631c2018-03-28 19:17:34 +020086 MainLoop.register_poll_func(self.poll)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020087
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 Pedrol9a4631c2018-03-28 19:17:34 +020093 MainLoop.unregister_poll_func(self.poll)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020094 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200102 lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) )
103 self.client.set_message_received_handler(self._message_received_handler)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200104 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 Pedrol522ae682017-07-31 17:25:15 +0200108 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 +0200109 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200120 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 Pedrol69373b32017-08-17 17:01:50 +0200125 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200128
129 def receipt_was_received(self, umref):
Pau Espin Pedrol3169ffb2017-08-18 17:22:19 +0200130 return umref not in self.references_pending_receipt
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200131
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200132 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200140 self.dbg('Expected failure triggered: %d' % errcode)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200141
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200142 def sms_send(self, sms_obj, mode, receipt=False):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200143 parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(str(sms_obj))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200144 seqs = []
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200145 self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn()))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200146 umref = self.next_user_message_reference
Pau Espin Pedrol5adb5c72017-08-17 17:57:58 +0200147 self.next_user_message_reference = (self.next_user_message_reference + 1) % (1 << 8)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200148 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 Pedroldb0d8ab2017-07-05 13:38:33 +0200158 esm_class=mode,
159 registered_delivery=receipt,
160 user_message_reference=umref,
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200161 )
162
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200163 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 Pedrol91c75b32017-07-20 22:41:22 +0200171 if pdu.sequence in self.pdus_pending:
172 self.pdus_pending.remove(pdu.sequence)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200173
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 Pedrol9a4631c2018-03-28 19:17:34 +0200180 MainLoop.wait(self, lambda: len(self.pdus_pending) == 0, timeout=10)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200181 return umref
182 finally:
183 self.client.set_message_sent_handler(old_func)
184
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200185# vim: expandtab tabstop=4 shiftwidth=4