blob: f92863d5a02eb12946a775df61fcf34e60ce0a9d [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
22import smpplib.consts
23import smpplib.exceptions
24
25from . import log, util, event_loop, sms
26
27# if you want to know what's happening inside python-smpplib
28#import logging
29#logging.basicConfig(level='DEBUG')
30
31MAX_SYS_ID_LEN = 16
32MAX_PASSWD_LEN = 16
33
34class Esme(log.Origin):
35 client = None
36 smsc = None
37
38 def __init__(self, msisdn):
39 self.msisdn = msisdn
40 # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator.
41 self.set_system_id('esme-' + self.msisdn[-11:])
42 super().__init__(log.C_TST, self.system_id)
43 self.set_password('esme-pwd')
44 self.connected = False
45 self.bound = False
46 self.listening = False
47
48 def __del__(self):
49 try:
50 self.disconnect()
51 except smpplib.exceptions.ConnectionError:
52 pass
53
54 def set_smsc(self, smsc):
55 self.smsc = smsc
56
57 def set_system_id(self, name):
58 if len(name) > MAX_SYS_ID_LEN:
59 raise log.Error('Esme system_id too long! %d vs %d', len(name), MAX_SYS_ID_LEN)
60 self.system_id = name
61
62 def set_password(self, password):
63 if len(password) > MAX_PASSWD_LEN:
64 raise log.Error('Esme password too long! %d vs %d', len(password), MAX_PASSWD_LEN)
65 self.password = password
66
67 def conf_for_smsc(self):
68 config = { 'system_id': self.system_id, 'password': self.password }
69 return config
70
71 def poll(self):
72 self.client.poll()
73
74 def start_listening(self):
75 self.listening = True
76 event_loop.register_poll_func(self.poll)
77
78 def stop_listening(self):
79 if not self.listening:
80 return
81 self.listening = False
82 # Empty the queue before processing the unbind + disconnect PDUs
83 event_loop.unregister_poll_func(self.poll)
84 self.poll()
85
86 def connect(self):
87 host, port = self.smsc.addr_port
88 if self.client:
89 self.disconnect()
90 self.client = smpplib.client.Client(host, port, timeout=None)
91 self.client.set_message_sent_handler(
92 lambda pdu: self.dbg('message sent:', repr(pdu)) )
93 self.client.set_message_received_handler(
94 lambda pdu: self.dbg('message received:', repr(pdu)) )
95 self.client.connect()
96 self.connected = True
97 self.client.bind_transceiver(system_id=self.system_id, password=self.password)
98 self.bound = True
99 self.log('Connected and bound successfully. Starting to listen')
100 self.start_listening()
101
102 def disconnect(self):
103 self.stop_listening()
104 if self.bound:
105 self.client.unbind()
106 self.bound = False
107 if self.connected:
108 self.client.disconnect()
109 self.connected = False
110
111 def run_method_expect_failure(self, errcode, method, *args):
112 try:
113 method(*args)
114 #it should not succeed, raise an exception:
115 raise log.Error('SMPP Failure: %s should have failed with SMPP error %d (%s) but succeeded.' % (method, errcode, smpplib.consts.DESCRIPTIONS[errcode]))
116 except smpplib.exceptions.PDUError as e:
117 if e.args[1] != errcode:
118 raise e
119
120 def sms_send(self, sms_obj):
121 parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(str(sms_obj))
122
123 self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn()))
124 for part in parts:
125 pdu = self.client.send_message(
126 source_addr_ton=smpplib.consts.SMPP_TON_INTL,
127 source_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
128 source_addr=sms_obj.src_msisdn(),
129 dest_addr_ton=smpplib.consts.SMPP_TON_INTL,
130 dest_addr_npi=smpplib.consts.SMPP_NPI_ISDN,
131 destination_addr=sms_obj.dst_msisdn(),
132 short_message=part,
133 data_coding=encoding_flag,
134 esm_class=smpplib.consts.SMPP_MSGMODE_FORWARD,
135 registered_delivery=False,
136 )
137
138# vim: expandtab tabstop=4 shiftwidth=4