From db0d8ab4fcfac67e76729241d92ca128d4526240 Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Wed, 5 Jul 2017 13:38:33 +0200 Subject: Improve SMPP supported features and test coverage esme: Add several bits to handle logic required by tests: - Allow specifying the mode used to send an sms - Add a parameter to ask to receive a Delivery receipt for that message - Add sms_send_wait_resp API, which waits until the response message for a given smpp message is received when sending an sms. - Add receipt_was_received API, which together with message_received_handler maintains state of the delivery receipts we asked for and were still not received. However, the check needs to be disabled for now because OsmoNITB doens't seem to be sending stuff properly, see OsmoNITB #2353. - On message_received_handler, also print alert_notification messages, to show that there's actually a bug in OsmoNITB, see #2352. Move old esme_ms_sms to esme_ms_sms_transaction, and explicitly state that we are using that mode. On the same test, we can now enable the part which asserts that sending an SMS to an msisdn with unknown destination triggers an error. The issue was mainly that the error had to come from the SMSC server response, not from the sent message, so we have to wait for the response to have the failure triggered. Finally, add esme_ms_sms_storeforward, which tests features for sms sent using that mode, and uses the APIs described above. Change-Id: Ia2c0c325fee14143deca8310312fc530cd9ce92e --- src/osmo_gsm_tester/esme.py | 57 ++++++++++++++++++++++---- suites/aoip_smpp/esme_ms_sms.py | 56 -------------------------- suites/aoip_smpp/esme_ms_sms_storeforward.py | 60 ++++++++++++++++++++++++++++ suites/aoip_smpp/esme_ms_sms_transaction.py | 56 ++++++++++++++++++++++++++ suites/smpp/esme_ms_sms.py | 49 ----------------------- suites/smpp/esme_ms_sms_storeforward.py | 52 ++++++++++++++++++++++++ suites/smpp/esme_ms_sms_transaction.py | 48 ++++++++++++++++++++++ 7 files changed, 266 insertions(+), 112 deletions(-) delete mode 100755 suites/aoip_smpp/esme_ms_sms.py create mode 100755 suites/aoip_smpp/esme_ms_sms_storeforward.py create mode 100755 suites/aoip_smpp/esme_ms_sms_transaction.py delete mode 100755 suites/smpp/esme_ms_sms.py create mode 100755 suites/smpp/esme_ms_sms_storeforward.py create mode 100755 suites/smpp/esme_ms_sms_transaction.py diff --git a/src/osmo_gsm_tester/esme.py b/src/osmo_gsm_tester/esme.py index f92863d..ff403c0 100644 --- a/src/osmo_gsm_tester/esme.py +++ b/src/osmo_gsm_tester/esme.py @@ -19,6 +19,7 @@ import smpplib.gsm import smpplib.client +import smpplib.command import smpplib.consts import smpplib.exceptions @@ -35,6 +36,9 @@ class Esme(log.Origin): client = None smsc = None + MSGMODE_TRANSACTION = smpplib.consts.SMPP_MSGMODE_FORWARD + MSGMODE_STOREFORWARD = smpplib.consts.SMPP_MSGMODE_STOREFORWARD + def __init__(self, msisdn): self.msisdn = msisdn # Get last characters of msisdn to stay inside MAX_SYS_ID_LEN. Similar to modulus operator. @@ -44,6 +48,8 @@ class Esme(log.Origin): self.connected = False self.bound = False self.listening = False + self.references_pending_receipt = [] + self.next_user_message_reference = 1 def __del__(self): try: @@ -89,9 +95,8 @@ class Esme(log.Origin): self.disconnect() self.client = smpplib.client.Client(host, port, timeout=None) self.client.set_message_sent_handler( - lambda pdu: self.dbg('message sent:', repr(pdu)) ) - self.client.set_message_received_handler( - lambda pdu: self.dbg('message received:', repr(pdu)) ) + lambda pdu: self.dbg('Unhandled submit_sm_resp message:', pdu.sequence) ) + self.client.set_message_received_handler(self._message_received_handler) self.client.connect() self.connected = True self.client.bind_transceiver(system_id=self.system_id, password=self.password) @@ -108,6 +113,19 @@ class Esme(log.Origin): self.client.disconnect() self.connected = False + def _message_received_handler(self, pdu, *args): + self.dbg('message received:', seq=pdu.sequence) + if isinstance(pdu, smpplib.command.AlertNotification): + self.dbg('message received: AlertNotification:', ms_availability_status=pdu.ms_availability_status) + elif isinstance(pdu, smpplib.command.DeliverSM): + self.dbg('message received:', user_message_reference=pdu.user_message_reference, references_pending_receipt=self.references_pending_receipt) + self.references_pending_receipt.remove(pdu.user_message_reference) + + def receipt_was_received(self, umref): + # return umref not in self.references_pending_receipt + self.log('FIXME: wait_receipt disabled because receipts are not received, see OsmoNITB #2353') + return True + def run_method_expect_failure(self, errcode, method, *args): try: method(*args) @@ -116,11 +134,14 @@ class Esme(log.Origin): except smpplib.exceptions.PDUError as e: if e.args[1] != errcode: raise e + self.dbg('Expected failure triggered: %d' % errcode) - def sms_send(self, sms_obj): + def sms_send(self, sms_obj, mode, receipt=False): parts, encoding_flag, msg_type_flag = smpplib.gsm.make_parts(str(sms_obj)) - + seqs = [] self.log('Sending SMS "%s" to %s' % (str(sms_obj), sms_obj.dst_msisdn())) + umref = self.next_user_message_reference + self.next_user_message_reference += 1 for part in parts: pdu = self.client.send_message( source_addr_ton=smpplib.consts.SMPP_TON_INTL, @@ -131,8 +152,30 @@ class Esme(log.Origin): destination_addr=sms_obj.dst_msisdn(), short_message=part, data_coding=encoding_flag, - esm_class=smpplib.consts.SMPP_MSGMODE_FORWARD, - registered_delivery=False, + esm_class=mode, + registered_delivery=receipt, + user_message_reference=umref, ) + self.dbg('sent part with seq', pdu.sequence) + seqs.append(pdu.sequence) + if receipt: + self.references_pending_receipt.append(umref) + return umref, seqs + + def _process_pdus_pending(self, pdu, **kwargs): + self.dbg('message sent resp with seq', pdu.sequence, ', pdus_pending:', self.pdus_pending) + self.pdus_pending.remove(pdu.sequence) + + def sms_send_wait_resp(self, sms_obj, mode, receipt=False): + old_func = self.client.message_sent_handler + try: + umref, self.pdus_pending = self.sms_send(sms_obj, mode, receipt) + self.dbg('pdus_pending:', self.pdus_pending) + self.client.set_message_sent_handler(self._process_pdus_pending) + event_loop.wait(self, lambda: len(self.pdus_pending) == 0, timeout=10) + return umref + finally: + self.client.set_message_sent_handler(old_func) + # vim: expandtab tabstop=4 shiftwidth=4 diff --git a/suites/aoip_smpp/esme_ms_sms.py b/suites/aoip_smpp/esme_ms_sms.py deleted file mode 100755 index 7f9ef18..0000000 --- a/suites/aoip_smpp/esme_ms_sms.py +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env python3 - -# This test checks following use-cases: -# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously -# defined in its configuration file. -# * ESME can send an SMS to an already registered MS when SMSC is in 'forward' mode. - -from osmo_gsm_tester.test import * - -SMPP_ESME_RINVDSTADR = 0x0000000B - -hlr = suite.hlr() -bts = suite.bts() -mgcpgw = suite.mgcpgw(bts_ip=bts.remote_addr()) -msc = suite.msc(hlr, mgcpgw) -bsc = suite.bsc(msc) -stp = suite.stp() -bsc.bts_add(bts) - -ms = suite.modem() -esme = suite.esme() -msc.smsc.esme_add(esme) - -hlr.start() -stp.start() -msc.start() -mgcpgw.start() -bsc.start() -bts.start() - -esme.connect() -hlr.subscriber_add(ms) -ms.connect(msc.mcc_mnc()) - -ms.log_info() -print('waiting for modem to attach...') -wait(ms.is_connected, msc.mcc_mnc()) -wait(msc.subscriber_attached, ms) - -print('sending first sms...') -msg = Sms(esme.msisdn, ms.msisdn, 'smpp send message') -esme.sms_send(msg) -wait(ms.sms_was_received, msg) - -print('sending second sms (unicode chars not in gsm aplhabet)...') -msg = Sms(esme.msisdn, ms.msisdn, 'chars:[кизаçйж]') -esme.sms_send(msg) -wait(ms.sms_was_received, msg) - -# FIXME: This test is not failing with error but succeeds, need to check why: (forward vs store policy?) -# wrong_msisdn = ms.msisdn + esme.msisdn -# print('sending third sms (with wrong msisdn %s)' % wrong_msisdn) -# msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest') -# esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send, msg) - -esme.disconnect() diff --git a/suites/aoip_smpp/esme_ms_sms_storeforward.py b/suites/aoip_smpp/esme_ms_sms_storeforward.py new file mode 100755 index 0000000..3e7e4f8 --- /dev/null +++ b/suites/aoip_smpp/esme_ms_sms_storeforward.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +# This test checks following use-cases: +# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously +# defined in its configuration file. +# * When SMS is sent in 'store & forward' mode, ESME fails to send an SMS to non registered MS. +# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to a not yet registered MS. +# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to an already registered MS. +# * When SMS is sent in 'store & forward' mode, ESME receives a SMS receipt if it asked for it. + +from osmo_gsm_tester.test import * + +SMPP_ESME_RINVDSTADR = 0x0000000B + +hlr = suite.hlr() +bts = suite.bts() +mgcpgw = suite.mgcpgw(bts_ip=bts.remote_addr()) +msc = suite.msc(hlr, mgcpgw) +bsc = suite.bsc(msc) +stp = suite.stp() +bsc.bts_add(bts) + +ms = suite.modem() +esme = suite.esme() +msc.smsc.esme_add(esme) + +hlr.start() +stp.start() +msc.start() +mgcpgw.start() +bsc.start() +bts.start() + +esme.connect() +hlr.subscriber_add(ms) + +wrong_msisdn = ms.msisdn + esme.msisdn +print('sending sms with wrong msisdn %s, it will fail' % wrong_msisdn) +msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest') +esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_STOREFORWARD) + +print('sending sms, it will be stored...') +msg = Sms(esme.msisdn, ms.msisdn, 'smpp send not-yet-registered message') +umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True) + +print('MS registers and will receive the SMS...') +ms.connect(msc.mcc_mnc()) +wait(ms.is_connected, msc.mcc_mnc()) +wait(msc.subscriber_attached, ms) +wait(ms.sms_was_received, msg) +print('Waiting to receive and consume sms receipt with reference', umref) +wait(esme.receipt_was_received, umref) + +print('checking MS can receive SMS while registered...') +msg = Sms(esme.msisdn, ms.msisdn, 'smpp send already-registered message') +umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True) +wait(ms.sms_was_received, msg) +print('Waiting to receive and consume sms receipt with reference', umref) +wait(esme.receipt_was_received, umref) +esme.disconnect() diff --git a/suites/aoip_smpp/esme_ms_sms_transaction.py b/suites/aoip_smpp/esme_ms_sms_transaction.py new file mode 100755 index 0000000..8bcfb6b --- /dev/null +++ b/suites/aoip_smpp/esme_ms_sms_transaction.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 + +# This test checks following use-cases: +# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously +# defined in its configuration file. +# * When SMS is sent in 'transaction' mode, ESME can send an SMS to an already registered MS. +# * When SMS is sent in 'transaction' mode, ESME fails to send an SMS to non registered MS. + +from osmo_gsm_tester.test import * + +SMPP_ESME_RINVDSTADR = 0x0000000B + +hlr = suite.hlr() +bts = suite.bts() +mgcpgw = suite.mgcpgw(bts_ip=bts.remote_addr()) +msc = suite.msc(hlr, mgcpgw) +bsc = suite.bsc(msc) +stp = suite.stp() +bsc.bts_add(bts) + +ms = suite.modem() +esme = suite.esme() +msc.smsc.esme_add(esme) + +hlr.start() +stp.start() +msc.start() +mgcpgw.start() +bsc.start() +bts.start() + +esme.connect() +hlr.subscriber_add(ms) +ms.connect(msc.mcc_mnc()) + +ms.log_info() +print('waiting for modem to attach...') +wait(ms.is_connected, msc.mcc_mnc()) +wait(msc.subscriber_attached, ms) + +print('sending first sms...') +msg = Sms(esme.msisdn, ms.msisdn, 'smpp send message') +esme.sms_send(msg, esme.MSGMODE_TRANSACTION) +wait(ms.sms_was_received, msg) + +print('sending second sms (unicode chars not in gsm aplhabet)...') +msg = Sms(esme.msisdn, ms.msisdn, 'chars:[кизаçйж]') +esme.sms_send(msg, esme.MSGMODE_TRANSACTION) +wait(ms.sms_was_received, msg) + +wrong_msisdn = ms.msisdn + esme.msisdn +print('sending third sms (with wrong msisdn %s)' % wrong_msisdn) +msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest') +esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_TRANSACTION) + +esme.disconnect() diff --git a/suites/smpp/esme_ms_sms.py b/suites/smpp/esme_ms_sms.py deleted file mode 100755 index bc9d7d4..0000000 --- a/suites/smpp/esme_ms_sms.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 - -# This test checks following use-cases: -# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously -# defined in its configuration file. -# * ESME can send an SMS to an already registered MS when SMSC is in 'forward' mode. - -from osmo_gsm_tester.test import * - -SMPP_ESME_RINVDSTADR = 0x0000000B - -nitb = suite.nitb() -bts = suite.bts() -ms = suite.modem() -esme = suite.esme() - -print('start nitb and bts...') -nitb.bts_add(bts) -nitb.smsc.esme_add(esme) -nitb.start() -bts.start() - -esme.connect() -nitb.subscriber_add(ms) -ms.connect(nitb.mcc_mnc()) - -ms.log_info() -print('waiting for modem to attach...') -wait(ms.is_connected, nitb.mcc_mnc()) -wait(nitb.subscriber_attached, ms) - -print('sending first sms...') -msg = Sms(esme.msisdn, ms.msisdn, 'smpp send message') -esme.sms_send(msg) -wait(ms.sms_was_received, msg) - -print('sending second sms (unicode chars not in gsm aplhabet)...') -msg = Sms(esme.msisdn, ms.msisdn, 'chars:[кизаçйж]') -esme.sms_send(msg) -wait(ms.sms_was_received, msg) - - -# FIXME: This test is not failing with error but succeeds, need to check why: (forward vs store policy?) -# wrong_msisdn = ms.msisdn + esme.msisdn -# print('sending third sms (with wrong msisdn %s)' % wrong_msisdn) -# msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest') -# esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send, msg) - -esme.disconnect() diff --git a/suites/smpp/esme_ms_sms_storeforward.py b/suites/smpp/esme_ms_sms_storeforward.py new file mode 100755 index 0000000..5ff6ad3 --- /dev/null +++ b/suites/smpp/esme_ms_sms_storeforward.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python3 + +# This test checks following use-cases: +# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously +# defined in its configuration file. +# * When SMS is sent in 'store & forward' mode, ESME fails to send an SMS to non registered MS. +# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to a not yet registered MS. +# * When SMS is sent in 'store & forward' mode, ESME can send an SMS to an already registered MS. +# * When SMS is sent in 'store & forward' mode, ESME receives a SMS receipt if it asked for it. + +from osmo_gsm_tester.test import * + +SMPP_ESME_RINVDSTADR = 0x0000000B + +nitb = suite.nitb() +bts = suite.bts() +ms = suite.modem() +esme = suite.esme() + +print('start nitb and bts...') +nitb.bts_add(bts) +nitb.smsc.esme_add(esme) +nitb.start() +bts.start() + +esme.connect() +nitb.subscriber_add(ms) + +wrong_msisdn = ms.msisdn + esme.msisdn +print('sending sms with wrong msisdn %s, it will fail' % wrong_msisdn) +msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest') +esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_STOREFORWARD) + +print('sending sms, it will be stored...') +msg = Sms(esme.msisdn, ms.msisdn, 'smpp send not-yet-registered message') +umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True) + +print('MS registers and will receive the SMS...') +ms.connect(nitb.mcc_mnc()) +wait(ms.is_connected, nitb.mcc_mnc()) +wait(nitb.subscriber_attached, ms) +wait(ms.sms_was_received, msg) +print('Waiting to receive and consume sms receipt with reference', umref) +wait(esme.receipt_was_received, umref) + +print('checking MS can receive SMS while registered...') +msg = Sms(esme.msisdn, ms.msisdn, 'smpp send already-registered message') +umref = esme.sms_send_wait_resp(msg, esme.MSGMODE_STOREFORWARD, receipt=True) +wait(ms.sms_was_received, msg) +print('Waiting to receive and consume sms receipt with reference', umref) +wait(esme.receipt_was_received, umref) +esme.disconnect() diff --git a/suites/smpp/esme_ms_sms_transaction.py b/suites/smpp/esme_ms_sms_transaction.py new file mode 100755 index 0000000..a147754 --- /dev/null +++ b/suites/smpp/esme_ms_sms_transaction.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +# This test checks following use-cases: +# * SMPP interface of SMSC accepts SMPP clients (ESMEs) with password previously +# defined in its configuration file. +# * When SMS is sent in 'transaction' mode, ESME can send an SMS to an already registered MS. +# * When SMS is sent in 'transaction' mode, ESME fails to send an SMS to non registered MS. + +from osmo_gsm_tester.test import * + +SMPP_ESME_RINVDSTADR = 0x0000000B + +nitb = suite.nitb() +bts = suite.bts() +ms = suite.modem() +esme = suite.esme() + +print('start nitb and bts...') +nitb.bts_add(bts) +nitb.smsc.esme_add(esme) +nitb.start() +bts.start() + +esme.connect() +nitb.subscriber_add(ms) +ms.connect(nitb.mcc_mnc()) + +ms.log_info() +print('waiting for modem to attach...') +wait(ms.is_connected, nitb.mcc_mnc()) +wait(nitb.subscriber_attached, ms) + +print('sending first sms...') +msg = Sms(esme.msisdn, ms.msisdn, 'smpp send message') +esme.sms_send(msg, esme.MSGMODE_TRANSACTION) +wait(ms.sms_was_received, msg) + +print('sending second sms (unicode chars not in gsm aplhabet)...') +msg = Sms(esme.msisdn, ms.msisdn, 'chars:[кизаçйж]') +esme.sms_send(msg, esme.MSGMODE_TRANSACTION) +wait(ms.sms_was_received, msg) + +wrong_msisdn = ms.msisdn + esme.msisdn +print('sending third sms (with wrong msisdn %s)' % wrong_msisdn) +msg = Sms(esme.msisdn, wrong_msisdn, 'smpp message with wrong dest') +esme.run_method_expect_failure(SMPP_ESME_RINVDSTADR, esme.sms_send_wait_resp, msg, esme.MSGMODE_TRANSACTION) + +esme.disconnect() -- cgit v1.2.3