blob: 9dbc5a36f117dda88c2d5c1f62fba3db944ce3cd [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
26from . import log, util, event_loop, sms
27
28# if you want to know what's happening inside python-smpplib
29#import logging
30#logging.basicConfig(level='DEBUG')
31
32MAX_SYS_ID_LEN = 16
33MAX_PASSWD_LEN = 16
34
35class Esme(log.Origin):
36 client = None
37 smsc = None
38
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020039 MSGMODE_TRANSACTION = smpplib.consts.SMPP_MSGMODE_FORWARD
40 MSGMODE_STOREFORWARD = smpplib.consts.SMPP_MSGMODE_STOREFORWARD
41
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020042 def __init__(self, msisdn):
43 self.msisdn = msisdn
44 # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator.
45 self.set_system_id('esme-' + self.msisdn[-11:])
46 super().__init__(log.C_TST, self.system_id)
47 self.set_password('esme-pwd')
48 self.connected = False
49 self.bound = False
50 self.listening = False
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +020051 self.references_pending_receipt = []
52 self.next_user_message_reference = 1
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020053
54 def __del__(self):
Pau Espin Pedrolac9c1bb2017-08-10 10:59:40 +020055 self.cleanup()
56
57 def cleanup(self):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020058 try:
59 self.disconnect()
60 except smpplib.exceptions.ConnectionError:
61 pass
62
63 def set_smsc(self, smsc):
64 self.smsc = smsc
65
66 def set_system_id(self, name):
67 if len(name) > MAX_SYS_ID_LEN:
68 raise log.Error('Esme system_id too long! %d vs %d', len(name), MAX_SYS_ID_LEN)
69 self.system_id = name
70
71 def set_password(self, password):
72 if len(password) > MAX_PASSWD_LEN:
73 raise log.Error('Esme password too long! %d vs %d', len(password), MAX_PASSWD_LEN)
74 self.password = password
75
76 def conf_for_smsc(self):
77 config = { 'system_id': self.system_id, 'password': self.password }
78 return config
79
80 def poll(self):
81 self.client.poll()
82
83 def start_listening(self):
84 self.listening = True
85 event_loop.register_poll_func(self.poll)
86
87 def stop_listening(self):
88 if not self.listening:
89 return
90 self.listening = False
91 # Empty the queue before processing the unbind + disconnect PDUs
92 event_loop.unregister_poll_func(self.poll)
93 self.poll()
94
95 def connect(self):
96 host, port = self.smsc.addr_port
97 if self.client:
98 self.disconnect()
99 self.client = smpplib.client.Client(host, port, timeout=None)
100 self.client.set_message_sent_handler(
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200101 lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) )
102 self.client.set_message_received_handler(self._message_received_handler)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200103 self.client.connect()
104 self.connected = True
105 self.client.bind_transceiver(system_id=self.system_id, password=self.password)
106 self.bound = True
Pau Espin Pedrol522ae682017-07-31 17:25:15 +0200107 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 +0200108 self.start_listening()
109
110 def disconnect(self):
111 self.stop_listening()
112 if self.bound:
113 self.client.unbind()
114 self.bound = False
115 if self.connected:
116 self.client.disconnect()
117 self.connected = False
118
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200119 def _message_received_handler(self, pdu, *args):
120 self.dbg('message received:', seq=pdu.sequence)
121 if isinstance(pdu, smpplib.command.AlertNotification):
122 self.dbg('message received: AlertNotification:', ms_availability_status=pdu.ms_availability_status)
123 elif isinstance(pdu, smpplib.command.DeliverSM):
Pau Espin Pedrol69373b32017-08-17 17:01:50 +0200124 umref = int(pdu.user_message_reference)
125 self.dbg('message received: DeliverSM', references_pending_receipt=self.references_pending_receipt, user_message_reference=umref)
126 self.references_pending_receipt.remove(umref)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200127
128 def receipt_was_received(self, umref):
129 # return umref not in self.references_pending_receipt
130 self.log('FIXME: wait_receipt disabled because receipts are not received, see OsmoNITB #2353')
131 return True
132
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200133 def run_method_expect_failure(self, errcode, method, *args):
134 try:
135 method(*args)
136 #it should not succeed, raise an exception:
137 raise log.Error('SMPP Failure: %s should have failed with SMPP error %d (%s) but succeeded.' % (method, errcode, smpplib.consts.DESCRIPTIONS[errcode]))
138 except smpplib.exceptions.PDUError as e:
139 if e.args[1] != errcode:
140 raise e
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200141 self.dbg('Expected failure triggered: %d' % errcode)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200142
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200143 def sms_send(self, sms_obj, mode, receipt=False):
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200144 parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(str(sms_obj))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200145 seqs = []
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200146 self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn()))
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200147 umref = self.next_user_message_reference
Pau Espin Pedrol5adb5c72017-08-17 17:57:58 +0200148 self.next_user_message_reference = (self.next_user_message_reference + 1) % (1 << 8)
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200149 for part in parts:
150 pdu = self.client.send_message(
151 source_addr_ton=smpplib.consts.SMPP_TON_INTL,
152 source_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
153 source_addr=sms_obj.src_msisdn(),
154 dest_addr_ton=smpplib.consts.SMPP_TON_INTL,
155 dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
156 destination_addr=sms_obj.dst_msisdn(),
157 short_message=part,
158 data_coding=encoding_flag,
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200159 esm_class=mode,
160 registered_delivery=receipt,
161 user_message_reference=umref,
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200162 )
163
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200164 self.dbg('sent part with seq', pdu.sequence)
165 seqs.append(pdu.sequence)
166 if receipt:
167 self.references_pending_receipt.append(umref)
168 return umref, seqs
169
170 def _process_pdus_pending(self, pdu, **kwargs):
171 self.dbg('message sent resp with seq', pdu.sequence, ', pdus_pending:', self.pdus_pending)
Pau Espin Pedrol91c75b32017-07-20 22:41:22 +0200172 if pdu.sequence in self.pdus_pending:
173 self.pdus_pending.remove(pdu.sequence)
Pau Espin Pedroldb0d8ab2017-07-05 13:38:33 +0200174
175 def sms_send_wait_resp(self, sms_obj, mode, receipt=False):
176 old_func = self.client.message_sent_handler
177 try:
178 umref, self.pdus_pending = self.sms_send(sms_obj, mode, receipt)
179 self.dbg('pdus_pending:', self.pdus_pending)
180 self.client.set_message_sent_handler(self._process_pdus_pending)
181 event_loop.wait(self, lambda: len(self.pdus_pending) == 0, timeout=10)
182 return umref
183 finally:
184 self.client.set_message_sent_handler(old_func)
185
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +0200186# vim: expandtab tabstop=4 shiftwidth=4