├── .gitignore ├── LICENCE ├── MANIFEST.in ├── README.rst ├── credentials_test.py ├── requirements.pip ├── run-tests.sh ├── setup.cfg ├── setup.py ├── smpp ├── __init__.py ├── clickatell.py ├── esme.py ├── pdu.py ├── pdu_builder.py ├── pdu_inspector.py └── smsc.py ├── test ├── __init__.py ├── pdu.py ├── pdu_asserts.py ├── pdu_hex.py ├── pdu_hex_asserts.py └── test_multipart.py └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.pyc 3 | *.egg-info 4 | ve/ 5 | *_priv.py 6 | .coverage 7 | coverage-html/ 8 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) Praekelt Foundation and individual contributors. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the Praekelt Foundation nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 21 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 22 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 24 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 26 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 27 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 28 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | exclude test/* 2 | exclude tests.py 3 | exclude *.egg-info/* 4 | include LICENCE 5 | include README.rst 6 | include requirements.pip 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Python SMPP 2 | =========== 3 | 4 | An SMPP version 3.4 library written in Python, suitable for use in Twisted_. 5 | 6 | To get started with development:: 7 | 8 | $ virtualenv --no-site-packages ve/ 9 | $ source ve/bin/activate 10 | (ve)$ pip install -r requirements.pip 11 | (ve)$ python 12 | >>> import smpp 13 | >>> 14 | 15 | Run the tests with nose 16 | 17 | (ve)$ nosetests 18 | 19 | .. _Twisted: http://www.twistedmatrix.com 20 | -------------------------------------------------------------------------------- /credentials_test.py: -------------------------------------------------------------------------------- 1 | 2 | logica = { 3 | 'host':'localhost', 4 | 'port':2775, 5 | 'system_id':'test id', 6 | 'password':'abc 123', 7 | } 8 | -------------------------------------------------------------------------------- /requirements.pip: -------------------------------------------------------------------------------- 1 | nose 2 | coverage 3 | pep8 4 | -------------------------------------------------------------------------------- /run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | virtualenv --no-site-packages ve && \ 3 | source ve/bin/activate && \ 4 | pip install -r requirements.pip && \ 5 | deactivate 6 | source ve/bin/activate && \ 7 | nosetests --with-doctest --with-coverage --cover-package=smpp --with-xunit && \ 8 | coverage xml smpp/* && \ 9 | pep8 --repeat --exclude '0*.py' smpp > pep8.txt && \ 10 | deactivate 11 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [nosetests] 2 | with-doctest=1 3 | nocapture=1 4 | with-coverage=1 5 | cover-package=smpp 6 | cover-html=1 7 | cover-html-dir=./coverage-html/ 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | def listify(filename): 4 | return filter(None, open(filename, 'r').readlines()) 5 | 6 | setup( 7 | name = "python-smpp", 8 | version = "0.1", 9 | url = 'http://github.com/dmaclay/python-smpp', 10 | license = 'BSD', 11 | description = "Python SMPP Library", 12 | long_description = open('README.rst','r').read(), 13 | author = 'David Maclay', 14 | author_email = 'dev@praekeltfoundation.org', 15 | packages = find_packages(), 16 | install_requires = ['setuptools'].extend(listify('requirements.pip')), 17 | classifiers = [ 18 | 'Development Status :: 4 - Beta', 19 | 'Intended Audience :: Developers', 20 | 'License :: OSI Approved :: BSD License', 21 | 'Operating System :: OS Independent', 22 | 'Programming Language :: Python', 23 | 'Topic :: Software Development :: Libraries :: Python Modules', 24 | ] 25 | ) 26 | 27 | -------------------------------------------------------------------------------- /smpp/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmaclay/python-smpp/4b37d211ea38f52ae282a469e377baabbcd17199/smpp/__init__.py -------------------------------------------------------------------------------- /smpp/clickatell.py: -------------------------------------------------------------------------------- 1 | 2 | clickatell_defaults = { 3 | 'interface_version':'34', 4 | 'dest_addr_ton':1, 5 | 'dest_addr_npi':1, 6 | } 7 | 8 | -------------------------------------------------------------------------------- /smpp/esme.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from pdu_builder import * 4 | 5 | 6 | class ESME(object): 7 | 8 | def __init__(self): 9 | self.state = 'CLOSED' 10 | self.sequence_number = 1 11 | self.conn = None 12 | self.defaults = { 13 | 'host':'127.0.0.1', 14 | 'port':2775, 15 | 'dest_addr_ton':0, 16 | 'dest_addr_npi':0, 17 | } 18 | 19 | 20 | def loadDefaults(self, defaults): 21 | self.defaults = dict(self.defaults, **defaults) 22 | 23 | 24 | def connect(self): 25 | if self.state in ['CLOSED']: 26 | self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 27 | self.conn.connect((self.defaults['host'], self.defaults['port'])) 28 | self.state = 'OPEN' 29 | 30 | 31 | def disconnect(self): 32 | if self.state in ['BOUND_TX', 'BOUND_RX', 'BOUND_TRX']: 33 | self.__unbind() 34 | if self.state in ['OPEN']: 35 | self.conn.close() 36 | self.state = 'CLOSED' 37 | 38 | 39 | def __recv(self): 40 | pdu = None 41 | length_bin = self.conn.recv(4) 42 | if not length_bin: 43 | return None 44 | else: 45 | #print 'length_bin', len(length_bin), length_bin 46 | if len(length_bin) == 4: 47 | length = int(binascii.b2a_hex(length_bin),16) 48 | rest_bin = self.conn.recv(length-4) 49 | pdu = unpack_pdu(length_bin + rest_bin) 50 | print '...', pdu['header']['sequence_number'], 51 | print '>', pdu['header']['command_id'], 52 | print '...', pdu['header']['command_status'] 53 | return pdu 54 | 55 | 56 | def __is_ok(self, pdu, id_check=None): 57 | if (isinstance(pdu, dict) 58 | and pdu.get('header',{}).get('command_status') == 'ESME_ROK' 59 | and (id_check == None 60 | or id_check == pdu['header'].get('command_id'))): 61 | return True 62 | else: 63 | return False 64 | 65 | 66 | def bind_transmitter(self): 67 | if self.state in ['CLOSED']: 68 | self.connect() 69 | if self.state in ['OPEN']: 70 | pdu = BindTransmitter(self.sequence_number, **self.defaults) 71 | self.conn.send(pdu.get_bin()) 72 | self.sequence_number +=1 73 | if self.__is_ok(self.__recv(), 'bind_transmitter_resp'): 74 | self.state = 'BOUND_TX' 75 | 76 | 77 | def __unbind(self): 78 | if self.state in ['BOUND_TX', 'BOUND_RX', 'BOUND_TRX']: 79 | pdu = Unbind(self.sequence_number) 80 | self.conn.send(pdu.get_bin()) 81 | self.sequence_number +=1 82 | if self.__is_ok(self.__recv(), 'unbind_resp'): 83 | self.state = 'OPEN' 84 | 85 | 86 | #def unbind(self): # will probably be deprecated 87 | #self.__unbind() 88 | 89 | 90 | def submit_sm(self, **kwargs): 91 | if self.state in ['BOUND_TX', 'BOUND_TRX']: 92 | print dict(self.defaults, **kwargs) 93 | pdu = SubmitSM(self.sequence_number, **dict(self.defaults, **kwargs)) 94 | self.conn.send(pdu.get_bin()) 95 | self.sequence_number +=1 96 | submit_sm_resp = self.__recv() 97 | #print self.__is_ok(submit_sm_resp, 'submit_sm_resp') 98 | 99 | 100 | def submit_multi(self, dest_address=[], **kwargs): 101 | if self.state in ['BOUND_TX', 'BOUND_TRX']: 102 | pdu = SubmitMulti(self.sequence_number, **dict(self.defaults, **kwargs)) 103 | for item in dest_address: 104 | if isinstance(item, str): # assume strings are addresses not lists 105 | pdu.addDestinationAddress( 106 | item, 107 | dest_addr_ton = self.defaults['dest_addr_ton'], 108 | dest_addr_npi = self.defaults['dest_addr_npi'], 109 | ) 110 | elif isinstance(item, dict): 111 | if item.get('dest_flag') == 1: 112 | pdu.addDestinationAddress( 113 | item.get('destination_addr', ''), 114 | dest_addr_ton = item.get('dest_addr_ton', 115 | self.defaults['dest_addr_ton']), 116 | dest_addr_npi = item.get('dest_addr_npi', 117 | self.defaults['dest_addr_npi']), 118 | ) 119 | elif item.get('dest_flag') == 2: 120 | pdu.addDistributionList(item.get('dl_name')) 121 | self.conn.send(pdu.get_bin()) 122 | self.sequence_number +=1 123 | submit_multi_resp = self.__recv() 124 | #print self.__is_ok(submit_multi_resp, 'submit_multi_resp') 125 | 126 | 127 | -------------------------------------------------------------------------------- /smpp/pdu.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import re 3 | try: 4 | import json 5 | except: 6 | import simplejson as json 7 | 8 | 9 | 10 | 11 | maps = {} # Inserting certain referenced dicts in here means they can be declared in the same order as in the spec. 12 | 13 | 14 | # SMPP PDU Definition - SMPP v3.4, section 4, page 45 15 | 16 | mandatory_parameter_lists = { 17 | 'bind_transmitter':[ # SMPP v3.4, section 4.1.1, table 4-1, page 46 18 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None}, 19 | {'name':'password', 'min':1, 'max':9, 'var':True, 'type':'string', 'map':None}, 20 | {'name':'system_type', 'min':1, 'max':13, 'var':True, 'type':'string', 'map':None}, 21 | {'name':'interface_version', 'min':1, 'max':1, 'var':False, 'type':'hex', 'map':None}, 22 | {'name':'addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 23 | {'name':'addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 24 | {'name':'address_range', 'min':1, 'max':41, 'var':True, 'type':'string', 'map':None} 25 | ], 26 | 'bind_transmitter_resp':[ # SMPP v3.4, section 4.1.2, table 4-2, page 47 27 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None} 28 | ], 29 | 'bind_receiver':[ # SMPP v3.4, section 4.1.3, table 4-3, page 48 30 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None}, 31 | {'name':'password', 'min':1, 'max':9, 'var':True, 'type':'string', 'map':None}, 32 | {'name':'system_type', 'min':1, 'max':13, 'var':True, 'type':'string', 'map':None}, 33 | {'name':'interface_version', 'min':1, 'max':1, 'var':False, 'type':'hex', 'map':None}, 34 | {'name':'addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 35 | {'name':'addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 36 | {'name':'address_range', 'min':1, 'max':41, 'var':True, 'type':'string', 'map':None} 37 | ], 38 | 'bind_receiver_resp':[ # SMPP v3.4, section 4.1.4, table 4-4, page 50 39 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None} 40 | ], 41 | 'bind_transceiver':[ # SMPP v3.4, section 4.1.5, table 4-5, page 51 42 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None}, 43 | {'name':'password', 'min':1, 'max':9, 'var':True, 'type':'string', 'map':None}, 44 | {'name':'system_type', 'min':1, 'max':13, 'var':True, 'type':'string', 'map':None}, 45 | {'name':'interface_version', 'min':1, 'max':1, 'var':False, 'type':'hex', 'map':None}, 46 | {'name':'addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 47 | {'name':'addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 48 | {'name':'address_range', 'min':1, 'max':41, 'var':True, 'type':'string', 'map':None} 49 | ], 50 | 'bind_transceiver_resp':[ # SMPP v3.4, section 4.1.6, table 4-6, page 52 51 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None} 52 | ], 53 | 'outbind':[ # SMPP v3.4, section 4.1.7.1, page 54 54 | {'name':'system_id', 'min':1, 'max':16, 'var':True, 'type':'string', 'map':None}, 55 | {'name':'password', 'min':1, 'max':9, 'var':True, 'type':'string', 'map':None} 56 | ], 57 | 'unbind':[ # SMPP v3.4, section 4.2.1, table 4-7, page 56 58 | ], 59 | 'unbind_resp':[ # SMPP v3.4, section 4.2.2, table 4-8, page 56 60 | ], 61 | 'generic_nack':[ # SMPP v3.4, section 4.3.1, table 4-9, page 57 62 | ], 63 | 'submit_sm':[ # SMPP v3.4, section 4.4.1, table 4-10, page 59-61 64 | {'name':'service_type', 'min':1, 'max':6, 'var':True, 'type':'string', 'map':None}, 65 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 66 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 67 | {'name':'source_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 68 | {'name':'dest_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 69 | {'name':'dest_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 70 | {'name':'destination_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 71 | {'name':'esm_class', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 72 | {'name':'protocol_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 73 | {'name':'priority_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 74 | {'name':'schedule_delivery_time', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 75 | {'name':'validity_period', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 76 | {'name':'registered_delivery', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 77 | {'name':'replace_if_present_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 78 | {'name':'data_coding', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 79 | {'name':'sm_default_msg_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 80 | {'name':'sm_length', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 81 | {'name':'short_message', 'min':0, 'max':254, 'var':'sm_length', 'type':'xstring', 'map':None} 82 | ], 83 | 'submit_sm_resp':[ # SMPP v3.4, section 4.4.2, table 4-11, page 67 84 | {'name':'message_id', 'min':0, 'max':65, 'var':True, 'type':'string', 'map':None} 85 | ], 86 | 'submit_multi':[ # SMPP v3.4, section 4.5.1, table 4-12, page 69-71 87 | {'name':'service_type', 'min':1, 'max':6, 'var':True, 'type':'string', 'map':None}, 88 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 89 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 90 | {'name':'source_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 91 | {'name':'number_of_dests', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 92 | {'name':'dest_address', 'min':0, 'max':0, 'var':'number_of_dests', 'type':'dest_address', 'map':None}, 93 | {'name':'esm_class', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 94 | {'name':'protocol_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 95 | {'name':'priority_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 96 | {'name':'schedule_delivery_time', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 97 | {'name':'validity_period', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 98 | {'name':'registered_delivery', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 99 | {'name':'replace_if_present_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 100 | {'name':'data_coding', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 101 | {'name':'sm_default_msg_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 102 | {'name':'sm_length', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 103 | {'name':'short_message', 'min':0, 'max':254, 'var':'sm_length', 'type':'xstring', 'map':None} 104 | ], 105 | 'dest_address':[ # SMPP v3.4, section 4.5.1.1, table 4-13, page 75 106 | {'name':'dest_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None} 107 | # 'sme_dest_address' or 'distribution_list' goes here 108 | ], 109 | 'sme_dest_address':[ # SMPP v3.4, section 4.5.1.1, table 4-14, page 75 110 | {'name':'dest_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 111 | {'name':'dest_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 112 | {'name':'destination_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None} 113 | ], 114 | 'distribution_list':[ # SMPP v3.4, section 4.5.1.2, table 4-15, page 75 115 | {'name':'dl_name', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None} 116 | ], 117 | 'submit_multi_resp':[ # SMPP v3.4, section 4.5.2, table 4-16, page 76 118 | {'name':'message_id', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 119 | {'name':'no_unsuccess', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 120 | {'name':'unsuccess_sme', 'min':0, 'max':0, 'var':'no_unsuccess', 'type':'unsuccess_sme', 'map':None} 121 | ], 122 | 'unsuccess_sme':[ # SMPP v3.4, section 4.5.2.1, table 4-17, page 77 123 | {'name':'dest_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 124 | {'name':'dest_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 125 | {'name':'destination_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 126 | {'name':'error_status_code', 'min':4, 'max':4, 'var':False, 'type':'integer', 'map':None} 127 | ], 128 | 'deliver_sm':[ # SMPP v3.4, section 4.6.1, table 4-18, page 79-81 129 | {'name':'service_type', 'min':1, 'max':6, 'var':True, 'type':'string', 'map':None}, 130 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 131 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 132 | {'name':'source_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 133 | {'name':'dest_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 134 | {'name':'dest_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 135 | {'name':'destination_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 136 | {'name':'esm_class', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 137 | {'name':'protocol_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 138 | {'name':'priority_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 139 | {'name':'schedule_delivery_time', 'min':1, 'max':1, 'var':False, 'type':'string', 'map':None}, 140 | {'name':'validity_period', 'min':1, 'max':1, 'var':False, 'type':'string', 'map':None}, 141 | {'name':'registered_delivery', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 142 | {'name':'replace_if_present_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 143 | {'name':'data_coding', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 144 | {'name':'sm_default_msg_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 145 | {'name':'sm_length', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 146 | {'name':'short_message', 'min':0, 'max':254, 'var':'sm_length', 'type':'xstring', 'map':None} 147 | ], 148 | 'deliver_sm_resp':[ # SMPP v3.4, section 4.6.2, table 4-19, page 85 149 | {'name':'message_id', 'min':1, 'max':1, 'var':False, 'type':'string', 'map':None} 150 | ], 151 | 'data_sm':[ # SMPP v3.4, section 4.7.1, table 4-20, page 87-88 152 | {'name':'service_type', 'min':1, 'max':6, 'var':True, 'type':'string', 'map':None}, 153 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 154 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 155 | {'name':'source_addr', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 156 | {'name':'dest_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 157 | {'name':'dest_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 158 | {'name':'destination_addr', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 159 | {'name':'esm_class', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 160 | {'name':'registered_delivery', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 161 | {'name':'data_coding', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None} 162 | ], 163 | 'data_sm_resp':[ # SMPP v3.4, section 4.7.2, table 4-21, page 93 164 | {'name':'message_id', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None} 165 | ], 166 | 'query_sm':[ # SMPP v3.4, section 4.8.1, table 4-22, page 95 167 | {'name':'message_id', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 168 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 169 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 170 | {'name':'source_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None} 171 | ], 172 | 'query_sm_resp':[ # SMPP v3.4, section 4.7.2, table 4-21, page 93 173 | {'name':'message_id', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 174 | {'name':'final_date', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 175 | {'name':'message_state', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 176 | {'name':'error_code', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None} 177 | ], 178 | 'cancel_sm':[ # SMPP v3.4, section 4.9.1, table 4-24, page 98-99 179 | {'name':'service_type', 'min':1, 'max':6, 'var':True, 'type':'string', 'map':None}, 180 | {'name':'message_id', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 181 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 182 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 183 | {'name':'source_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 184 | {'name':'dest_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 185 | {'name':'dest_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 186 | {'name':'destination_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None} 187 | ], 188 | 'cancel_sm_resp':[ # SMPP v3.4, section 4.9.2, table 4-25, page 100 189 | ], 190 | 'replace_sm':[ # SMPP v3.4, section 4.10.1, table 4-26, page 102-103 191 | {'name':'message_id', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 192 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 193 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 194 | {'name':'source_addr', 'min':1, 'max':21, 'var':True, 'type':'string', 'map':None}, 195 | {'name':'schedule_delivery_time', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 196 | {'name':'validity_period', 'min':1, 'max':17, 'var':False, 'type':'string', 'map':None}, 197 | {'name':'registered_delivery', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 198 | {'name':'replace_if_present_flag', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 199 | {'name':'data_coding', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 200 | {'name':'sm_default_msg_id', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 201 | {'name':'sm_length', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':None}, 202 | {'name':'short_message', 'min':0, 'max':254, 'var':'sm_length', 'type':'xstring', 'map':None} 203 | ], 204 | 'replace_sm_resp':[ # SMPP v3.4, section 4.10.2, table 4-27, page 104 205 | ], 206 | 'enquire_link':[ # SMPP v3.4, section 4.11.1, table 4-28, page 106 207 | ], 208 | 'enquire_link_resp':[ # SMPP v3.4, section 4.11.2, table 4-29, page 106 209 | ], 210 | 'alert_notification':[ # SMPP v3.4, section 4.12.1, table 4-30, page 108 211 | {'name':'source_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 212 | {'name':'source_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 213 | {'name':'source_addr', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 214 | {'name':'esme_addr_ton', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_ton'}, 215 | {'name':'esme_addr_npi', 'min':1, 'max':1, 'var':False, 'type':'integer', 'map':'addr_npi'}, 216 | {'name':'esme_addr', 'min':1, 'max':65, 'var':True, 'type':'string', 'map':None}, 217 | ] 218 | } 219 | def mandatory_parameter_list_by_command_name(command_name): 220 | return mandatory_parameter_lists.get(command_name,[]) 221 | 222 | 223 | # Command IDs - SMPP v3.4, section 5.1.2.1, table 5-1, page 110-111 224 | 225 | command_id_by_hex = { 226 | '80000000':{'hex':'80000000', 'name':'generic_nack'}, 227 | '00000001':{'hex':'00000001', 'name':'bind_receiver'}, 228 | '80000001':{'hex':'80000001', 'name':'bind_receiver_resp'}, 229 | '00000002':{'hex':'00000002', 'name':'bind_transmitter'}, 230 | '80000002':{'hex':'80000002', 'name':'bind_transmitter_resp'}, 231 | '00000003':{'hex':'00000003', 'name':'query_sm'}, 232 | '80000003':{'hex':'80000003', 'name':'query_sm_resp'}, 233 | '00000004':{'hex':'00000004', 'name':'submit_sm'}, 234 | '80000004':{'hex':'80000004', 'name':'submit_sm_resp'}, 235 | '00000005':{'hex':'00000005', 'name':'deliver_sm'}, 236 | '80000005':{'hex':'80000005', 'name':'deliver_sm_resp'}, 237 | '00000006':{'hex':'00000006', 'name':'unbind'}, 238 | '80000006':{'hex':'80000006', 'name':'unbind_resp'}, 239 | '00000007':{'hex':'00000007', 'name':'replace_sm'}, 240 | '80000007':{'hex':'80000007', 'name':'replace_sm_resp'}, 241 | '00000008':{'hex':'00000008', 'name':'cancel_sm'}, 242 | '80000008':{'hex':'80000008', 'name':'cancel_sm_resp'}, 243 | '00000009':{'hex':'00000009', 'name':'bind_transceiver'}, 244 | '80000009':{'hex':'80000009', 'name':'bind_transceiver_resp'}, 245 | '0000000b':{'hex':'0000000b', 'name':'outbind'}, 246 | '00000015':{'hex':'00000015', 'name':'enquire_link'}, 247 | '80000015':{'hex':'80000015', 'name':'enquire_link_resp'}, 248 | '00000021':{'hex':'00000021', 'name':'submit_multi'}, 249 | '80000021':{'hex':'80000021', 'name':'submit_multi_resp'}, 250 | '00000102':{'hex':'00000102', 'name':'alert_notification'}, 251 | '00000103':{'hex':'00000103', 'name':'data_sm'}, 252 | '80000103':{'hex':'80000103', 'name':'data_sm_resp'}, 253 | 254 | # v4 codes 255 | 256 | '80010000':{'hex':'80010000', 'name':'generic_nack_v4'}, 257 | '00010001':{'hex':'00010001', 'name':'bind_receiver_v4'}, 258 | '80010001':{'hex':'80010001', 'name':'bind_receiver_resp_v4'}, 259 | '00010002':{'hex':'00010002', 'name':'bind_transmitter_v4'}, 260 | '80010002':{'hex':'80010002', 'name':'bind_transmitter_resp_v4'}, 261 | '00010003':{'hex':'00010003', 'name':'query_sm_v4'}, 262 | '80010003':{'hex':'80010003', 'name':'query_sm_resp_v4'}, 263 | '00010004':{'hex':'00010004', 'name':'submit_sm_v4'}, 264 | '80010004':{'hex':'80010004', 'name':'submit_sm_resp_v4'}, 265 | '00010005':{'hex':'00010005', 'name':'deliver_sm_v4'}, 266 | '80010005':{'hex':'80010005', 'name':'deliver_sm_resp_v4'}, 267 | '00010006':{'hex':'00010006', 'name':'unbind_v4'}, 268 | '80010006':{'hex':'80010006', 'name':'unbind_resp_v4'}, 269 | '00010007':{'hex':'00010007', 'name':'replace_sm_v4'}, 270 | '80010007':{'hex':'80010007', 'name':'replace_sm_resp_v4'}, 271 | '00010008':{'hex':'00010008', 'name':'cancel_sm_v4'}, 272 | '80010008':{'hex':'80010008', 'name':'cancel_sm_resp_v4'}, 273 | '00010009':{'hex':'00010009', 'name':'delivery_receipt_v4'}, 274 | '80010009':{'hex':'80010009', 'name':'delivery_receipt_resp_v4'}, 275 | '0001000a':{'hex':'0001000a', 'name':'enquire_link_v4'}, 276 | '8001000a':{'hex':'8001000a', 'name':'enquire_link_resp_v4'}, 277 | '0001000b':{'hex':'0001000b', 'name':'outbind_v4'}, 278 | } 279 | def command_id_name_by_hex(x): 280 | return command_id_by_hex.get(x,{}).get('name') 281 | 282 | 283 | command_id_by_name = { 284 | 'generic_nack' :{'hex':'80000000', 'name':'generic_nack'}, 285 | 'bind_receiver' :{'hex':'00000001', 'name':'bind_receiver'}, 286 | 'bind_receiver_resp' :{'hex':'80000001', 'name':'bind_receiver_resp'}, 287 | 'bind_transmitter' :{'hex':'00000002', 'name':'bind_transmitter'}, 288 | 'bind_transmitter_resp' :{'hex':'80000002', 'name':'bind_transmitter_resp'}, 289 | 'query_sm' :{'hex':'00000003', 'name':'query_sm'}, 290 | 'query_sm_resp' :{'hex':'80000003', 'name':'query_sm_resp'}, 291 | 'submit_sm' :{'hex':'00000004', 'name':'submit_sm'}, 292 | 'submit_sm_resp' :{'hex':'80000004', 'name':'submit_sm_resp'}, 293 | 'deliver_sm' :{'hex':'00000005', 'name':'deliver_sm'}, 294 | 'deliver_sm_resp' :{'hex':'80000005', 'name':'deliver_sm_resp'}, 295 | 'unbind' :{'hex':'00000006', 'name':'unbind'}, 296 | 'unbind_resp' :{'hex':'80000006', 'name':'unbind_resp'}, 297 | 'replace_sm' :{'hex':'00000007', 'name':'replace_sm'}, 298 | 'replace_sm_resp' :{'hex':'80000007', 'name':'replace_sm_resp'}, 299 | 'cancel_sm' :{'hex':'00000008', 'name':'cancel_sm'}, 300 | 'cancel_sm_resp' :{'hex':'80000008', 'name':'cancel_sm_resp'}, 301 | 'bind_transceiver' :{'hex':'00000009', 'name':'bind_transceiver'}, 302 | 'bind_transceiver_resp' :{'hex':'80000009', 'name':'bind_transceiver_resp'}, 303 | 'outbind' :{'hex':'0000000b', 'name':'outbind'}, 304 | 'enquire_link' :{'hex':'00000015', 'name':'enquire_link'}, 305 | 'enquire_link_resp' :{'hex':'80000015', 'name':'enquire_link_resp'}, 306 | 'submit_multi' :{'hex':'00000021', 'name':'submit_multi'}, 307 | 'submit_multi_resp' :{'hex':'80000021', 'name':'submit_multi_resp'}, 308 | 'alert_notification' :{'hex':'00000102', 'name':'alert_notification'}, 309 | 'data_sm' :{'hex':'00000103', 'name':'data_sm'}, 310 | 'data_sm_resp' :{'hex':'80000103', 'name':'data_sm_resp'}, 311 | 312 | # v4 codes 313 | 314 | 'generic_nack_v4' :{'hex':'80010000', 'name':'generic_nack_v4'}, 315 | 'bind_receiver_v4' :{'hex':'00010001', 'name':'bind_receiver_v4'}, 316 | 'bind_receiver_resp_v4' :{'hex':'80010001', 'name':'bind_receiver_resp_v4'}, 317 | 'bind_transmitter_v4' :{'hex':'00010002', 'name':'bind_transmitter_v4'}, 318 | 'bind_transmitter_resp_v4':{'hex':'80010002', 'name':'bind_transmitter_resp_v4'}, 319 | 'query_sm_v4' :{'hex':'00010003', 'name':'query_sm_v4'}, 320 | 'query_sm_resp_v4' :{'hex':'80010003', 'name':'query_sm_resp_v4'}, 321 | 'submit_sm_v4' :{'hex':'00010004', 'name':'submit_sm_v4'}, 322 | 'submit_sm_resp_v4' :{'hex':'80010004', 'name':'submit_sm_resp_v4'}, 323 | 'deliver_sm_v4' :{'hex':'00010005', 'name':'deliver_sm_v4'}, 324 | 'deliver_sm_resp_v4' :{'hex':'80010005', 'name':'deliver_sm_resp_v4'}, 325 | 'unbind_v4' :{'hex':'00010006', 'name':'unbind_v4'}, 326 | 'unbind_resp_v4' :{'hex':'80010006', 'name':'unbind_resp_v4'}, 327 | 'replace_sm_v4' :{'hex':'00010007', 'name':'replace_sm_v4'}, 328 | 'replace_sm_resp_v4' :{'hex':'80010007', 'name':'replace_sm_resp_v4'}, 329 | 'cancel_sm_v4' :{'hex':'00010008', 'name':'cancel_sm_v4'}, 330 | 'cancel_sm_resp_v4' :{'hex':'80010008', 'name':'cancel_sm_resp_v4'}, 331 | 'delivery_receipt_v4' :{'hex':'00010009', 'name':'delivery_receipt_v4'}, 332 | 'delivery_receipt_resp_v4':{'hex':'80010009', 'name':'delivery_receipt_resp_v4'}, 333 | 'enquire_link_v4' :{'hex':'0001000a', 'name':'enquire_link_v4'}, 334 | 'enquire_link_resp_v4' :{'hex':'8001000a', 'name':'enquire_link_resp_v4'}, 335 | 'outbind_v4' :{'hex':'0001000b', 'name':'outbind_v4'} 336 | } 337 | def command_id_hex_by_name(n): 338 | return command_id_by_name.get(n,{}).get('hex') 339 | 340 | 341 | # SMPP Error Codes (ESME) - SMPP v3.4, section 5.1.3, table 5-2, page 112-114 342 | 343 | command_status_by_hex = { 344 | '00000000':{'hex':'00000000', 'name':'ESME_ROK', 'description':'No error'}, 345 | '00000001':{'hex':'00000001', 'name':'ESME_RINVMSGLEN', 'description':'Message Length is invalid'}, 346 | '00000002':{'hex':'00000002', 'name':'ESME_RINVCMDLEN', 'description':'Command Length is invalid'}, 347 | '00000003':{'hex':'00000003', 'name':'ESME_RINVCMDID', 'description':'Invalid Command ID'}, 348 | '00000004':{'hex':'00000004', 'name':'ESME_RINVBNDSTS', 'description':'Incorrect BIND Status for given command'}, 349 | '00000005':{'hex':'00000005', 'name':'ESME_RALYBND', 'description':'ESME Already in bound state'}, 350 | '00000006':{'hex':'00000006', 'name':'ESME_RINVPRTFLG', 'description':'Invalid priority flag'}, 351 | '00000007':{'hex':'00000007', 'name':'ESME_RINVREGDLVFLG', 'description':'Invalid registered delivery flag'}, 352 | '00000008':{'hex':'00000008', 'name':'ESME_RSYSERR', 'description':'System Error'}, 353 | '0000000a':{'hex':'0000000a', 'name':'ESME_RINVSRCADR', 'description':'Invalid source address'}, 354 | '0000000b':{'hex':'0000000b', 'name':'ESME_RINVDSTADR', 'description':'Invalid destination address'}, 355 | '0000000c':{'hex':'0000000c', 'name':'ESME_RINVMSGID', 'description':'Message ID is invalid'}, 356 | '0000000d':{'hex':'0000000d', 'name':'ESME_RBINDFAIL', 'description':'Bind failed'}, 357 | '0000000e':{'hex':'0000000e', 'name':'ESME_RINVPASWD', 'description':'Invalid password'}, 358 | '0000000f':{'hex':'0000000f', 'name':'ESME_RINVSYSID', 'description':'Invalid System ID'}, 359 | '00000011':{'hex':'00000011', 'name':'ESME_RCANCELFAIL', 'description':'Cancel SM Failed'}, 360 | '00000013':{'hex':'00000013', 'name':'ESME_RREPLACEFAIL', 'description':'Replace SM Failed'}, 361 | '00000014':{'hex':'00000014', 'name':'ESME_RMSGQFUL', 'description':'Message queue full'}, 362 | '00000015':{'hex':'00000015', 'name':'ESME_RINVSERTYP', 'description':'Invalid service type'}, 363 | '00000033':{'hex':'00000033', 'name':'ESME_RINVNUMDESTS', 'description':'Invalid number of destinations'}, 364 | '00000034':{'hex':'00000034', 'name':'ESME_RINVDLNAME', 'description':'Invalid distribution list name'}, 365 | '00000040':{'hex':'00000040', 'name':'ESME_RINVDESTFLAG', 'description':'Destination flag is invalid (submit_multi)'}, 366 | '00000042':{'hex':'00000042', 'name':'ESME_RINVSUBREP', 'description':"Invalid `submit with replace' request (i.e. submit_sm with replace_if_present_flag set)"}, 367 | '00000043':{'hex':'00000043', 'name':'ESME_RINVESMCLASS', 'description':'Invalid esm_class field data'}, 368 | '00000044':{'hex':'00000044', 'name':'ESME_RCNTSUBDL', 'description':'Cannot submit to distribution list'}, 369 | '00000045':{'hex':'00000045', 'name':'ESME_RSUBMITFAIL', 'description':'submit_sm or submit_multi failed'}, 370 | '00000048':{'hex':'00000048', 'name':'ESME_RINVSRCTON', 'description':'Invalid source address TON'}, 371 | '00000049':{'hex':'00000049', 'name':'ESME_RINVSRCNPI', 'description':'Invalid source address NPI'}, 372 | '00000050':{'hex':'00000050', 'name':'ESME_RINVDSTTON', 'description':'Invalid destination address TON'}, 373 | '00000051':{'hex':'00000051', 'name':'ESME_RINVDSTNPI', 'description':'Invalid destination address NPI'}, 374 | '00000053':{'hex':'00000053', 'name':'ESME_RINVSYSTYP', 'description':'Invalid system_type field'}, 375 | '00000054':{'hex':'00000054', 'name':'ESME_RINVREPFLAG', 'description':'Invalid replace_if_present flag'}, 376 | '00000055':{'hex':'00000055', 'name':'ESME_RINVNUMMSGS', 'description':'Invalid number of messages'}, 377 | '00000058':{'hex':'00000058', 'name':'ESME_RTHROTTLED', 'description':'Throttling error (ESME has exceeded allowed message limits)'}, 378 | '00000061':{'hex':'00000061', 'name':'ESME_RINVSCHED', 'description':'Invalid scheduled delivery time'}, 379 | '00000062':{'hex':'00000062', 'name':'ESME_RINVEXPIRY', 'description':'Invalid message validity period (expiry time)'}, 380 | '00000063':{'hex':'00000063', 'name':'ESME_RINVDFTMSGID', 'description':'Predefined message invalid or not found'}, 381 | '00000064':{'hex':'00000064', 'name':'ESME_RX_T_APPN', 'description':'ESME Receiver Temporary App Error Code'}, 382 | '00000065':{'hex':'00000065', 'name':'ESME_RX_P_APPN', 'description':'ESME Receiver Permanent App Error Code'}, 383 | '00000066':{'hex':'00000066', 'name':'ESME_RX_R_APPN', 'description':'ESME Receiver Reject Message Error Code'}, 384 | '00000067':{'hex':'00000067', 'name':'ESME_RQUERYFAIL', 'description':'query_sm request failed'}, 385 | '000000c0':{'hex':'000000c0', 'name':'ESME_RINVOPTPARSTREAM', 'description':'Error in the optional part of the PDU Body'}, 386 | '000000c1':{'hex':'000000c1', 'name':'ESME_ROPTPARNOTALLWD', 'description':'Optional paramenter not allowed'}, 387 | '000000c2':{'hex':'000000c2', 'name':'ESME_RINVPARLEN', 'description':'Invalid parameter length'}, 388 | '000000c3':{'hex':'000000c3', 'name':'ESME_RMISSINGOPTPARAM', 'description':'Expected optional parameter missing'}, 389 | '000000c4':{'hex':'000000c4', 'name':'ESME_RINVOPTPARAMVAL', 'description':'Invalid optional parameter value'}, 390 | '000000fe':{'hex':'000000fe', 'name':'ESME_RDELIVERYFAILURE', 'description':'Delivery Failure (used for data_sm_resp)'}, 391 | '000000ff':{'hex':'000000ff', 'name':'ESME_RUNKNOWNERR', 'description':'Unknown error'} 392 | } 393 | def command_status_name_by_hex(x): 394 | return command_status_by_hex.get(x,{}).get('name') 395 | 396 | command_status_by_name = { 397 | 'ESME_ROK' :{'hex':'00000000', 'name':'ESME_ROK', 'description':'No error'}, 398 | 'ESME_RINVMSGLEN' :{'hex':'00000001', 'name':'ESME_RINVMSGLEN', 'description':'Message Length is invalid'}, 399 | 'ESME_RINVCMDLEN' :{'hex':'00000002', 'name':'ESME_RINVCMDLEN', 'description':'Command Length is invalid'}, 400 | 'ESME_RINVCMDID' :{'hex':'00000003', 'name':'ESME_RINVCMDID', 'description':'Invalid Command ID'}, 401 | 'ESME_RINVBNDSTS' :{'hex':'00000004', 'name':'ESME_RINVBNDSTS', 'description':'Incorrect BIND Status for given command'}, 402 | 'ESME_RALYBND' :{'hex':'00000005', 'name':'ESME_RALYBND', 'description':'ESME Already in bound state'}, 403 | 'ESME_RINVPRTFLG' :{'hex':'00000006', 'name':'ESME_RINVPRTFLG', 'description':'Invalid priority flag'}, 404 | 'ESME_RINVREGDLVFLG' :{'hex':'00000007', 'name':'ESME_RINVREGDLVFLG', 'description':'Invalid registered delivery flag'}, 405 | 'ESME_RSYSERR' :{'hex':'00000008', 'name':'ESME_RSYSERR', 'description':'System Error'}, 406 | 'ESME_RINVSRCADR' :{'hex':'0000000a', 'name':'ESME_RINVSRCADR', 'description':'Invalid source address'}, 407 | 'ESME_RINVDSTADR' :{'hex':'0000000b', 'name':'ESME_RINVDSTADR', 'description':'Invalid destination address'}, 408 | 'ESME_RINVMSGID' :{'hex':'0000000c', 'name':'ESME_RINVMSGID', 'description':'Message ID is invalid'}, 409 | 'ESME_RBINDFAIL' :{'hex':'0000000d', 'name':'ESME_RBINDFAIL', 'description':'Bind failed'}, 410 | 'ESME_RINVPASWD' :{'hex':'0000000e', 'name':'ESME_RINVPASWD', 'description':'Invalid password'}, 411 | 'ESME_RINVSYSID' :{'hex':'0000000f', 'name':'ESME_RINVSYSID', 'description':'Invalid System ID'}, 412 | 'ESME_RCANCELFAIL' :{'hex':'00000011', 'name':'ESME_RCANCELFAIL', 'description':'Cancel SM Failed'}, 413 | 'ESME_RREPLACEFAIL' :{'hex':'00000013', 'name':'ESME_RREPLACEFAIL', 'description':'Replace SM Failed'}, 414 | 'ESME_RMSGQFUL' :{'hex':'00000014', 'name':'ESME_RMSGQFUL', 'description':'Message queue full'}, 415 | 'ESME_RINVSERTYP' :{'hex':'00000015', 'name':'ESME_RINVSERTYP', 'description':'Invalid service type'}, 416 | 'ESME_RINVNUMDESTS' :{'hex':'00000033', 'name':'ESME_RINVNUMDESTS', 'description':'Invalid number of destinations'}, 417 | 'ESME_RINVDLNAME' :{'hex':'00000034', 'name':'ESME_RINVDLNAME', 'description':'Invalid distribution list name'}, 418 | 'ESME_RINVDESTFLAG' :{'hex':'00000040', 'name':'ESME_RINVDESTFLAG', 'description':'Destination flag is invalid (submit_multi)'}, 419 | 'ESME_RINVSUBREP' :{'hex':'00000042', 'name':'ESME_RINVSUBREP', 'description':"Invalid `submit with replace' request (i.e. submit_sm with replace_if_present_flag set)"}, 420 | 'ESME_RINVESMCLASS' :{'hex':'00000043', 'name':'ESME_RINVESMCLASS', 'description':'Invalid esm_class field data'}, 421 | 'ESME_RCNTSUBDL' :{'hex':'00000044', 'name':'ESME_RCNTSUBDL', 'description':'Cannot submit to distribution list'}, 422 | 'ESME_RSUBMITFAIL' :{'hex':'00000045', 'name':'ESME_RSUBMITFAIL', 'description':'submit_sm or submit_multi failed'}, 423 | 'ESME_RINVSRCTON' :{'hex':'00000048', 'name':'ESME_RINVSRCTON', 'description':'Invalid source address TON'}, 424 | 'ESME_RINVSRCNPI' :{'hex':'00000049', 'name':'ESME_RINVSRCNPI', 'description':'Invalid source address NPI'}, 425 | 'ESME_RINVDSTTON' :{'hex':'00000050', 'name':'ESME_RINVDSTTON', 'description':'Invalid destination address TON'}, 426 | 'ESME_RINVDSTNPI' :{'hex':'00000051', 'name':'ESME_RINVDSTNPI', 'description':'Invalid destination address NPI'}, 427 | 'ESME_RINVSYSTYP' :{'hex':'00000053', 'name':'ESME_RINVSYSTYP', 'description':'Invalid system_type field'}, 428 | 'ESME_RINVREPFLAG' :{'hex':'00000054', 'name':'ESME_RINVREPFLAG', 'description':'Invalid replace_if_present flag'}, 429 | 'ESME_RINVNUMMSGS' :{'hex':'00000055', 'name':'ESME_RINVNUMMSGS', 'description':'Invalid number of messages'}, 430 | 'ESME_RTHROTTLED' :{'hex':'00000058', 'name':'ESME_RTHROTTLED', 'description':'Throttling error (ESME has exceeded allowed message limits)'}, 431 | 'ESME_RINVSCHED' :{'hex':'00000061', 'name':'ESME_RINVSCHED', 'description':'Invalid scheduled delivery time'}, 432 | 'ESME_RINVEXPIRY' :{'hex':'00000062', 'name':'ESME_RINVEXPIRY', 'description':'Invalid message validity period (expiry time)'}, 433 | 'ESME_RINVDFTMSGID' :{'hex':'00000063', 'name':'ESME_RINVDFTMSGID', 'description':'Predefined message invalid or not found'}, 434 | 'ESME_RX_T_APPN' :{'hex':'00000064', 'name':'ESME_RX_T_APPN', 'description':'ESME Receiver Temporary App Error Code'}, 435 | 'ESME_RX_P_APPN' :{'hex':'00000065', 'name':'ESME_RX_P_APPN', 'description':'ESME Receiver Permanent App Error Code'}, 436 | 'ESME_RX_R_APPN' :{'hex':'00000066', 'name':'ESME_RX_R_APPN', 'description':'ESME Receiver Reject Message Error Code'}, 437 | 'ESME_RQUERYFAIL' :{'hex':'00000067', 'name':'ESME_RQUERYFAIL', 'description':'query_sm request failed'}, 438 | 'ESME_RINVOPTPARSTREAM':{'hex':'000000c0', 'name':'ESME_RINVOPTPARSTREAM', 'description':'Error in the optional part of the PDU Body'}, 439 | 'ESME_ROPTPARNOTALLWD' :{'hex':'000000c1', 'name':'ESME_ROPTPARNOTALLWD', 'description':'Optional paramenter not allowed'}, 440 | 'ESME_RINVPARLEN' :{'hex':'000000c2', 'name':'ESME_RINVPARLEN', 'description':'Invalid parameter length'}, 441 | 'ESME_RMISSINGOPTPARAM':{'hex':'000000c3', 'name':'ESME_RMISSINGOPTPARAM', 'description':'Expected optional parameter missing'}, 442 | 'ESME_RINVOPTPARAMVAL' :{'hex':'000000c4', 'name':'ESME_RINVOPTPARAMVAL', 'description':'Invalid optional parameter value'}, 443 | 'ESME_RDELIVERYFAILURE':{'hex':'000000fe', 'name':'ESME_RDELIVERYFAILURE', 'description':'Delivery Failure (used for data_sm_resp)'}, 444 | 'ESME_RUNKNOWNERR' :{'hex':'000000ff', 'name':'ESME_RUNKNOWNERR', 'description':'Unknown error'} 445 | } 446 | def command_status_hex_by_name(n): 447 | return command_status_by_name.get(n,{}).get('hex') 448 | 449 | 450 | # Type of Number (TON) - SMPP v3.4, section 5.2.5, table 5-3, page 117 451 | 452 | maps['addr_ton_by_name'] = { 453 | 'unknown' :'00', 454 | 'international' :'01', 455 | 'national' :'02', 456 | 'network_specific' :'03', 457 | 'subscriber_number':'04', 458 | 'alphanumeric' :'05', 459 | 'abbreviated' :'06' 460 | } 461 | 462 | maps['addr_ton_by_hex'] = { 463 | '00':'unknown', 464 | '01':'international', 465 | '02':'national', 466 | '03':'network_specific', 467 | '04':'subscriber_number', 468 | '05':'alphanumeric', 469 | '06':'abbreviated' 470 | } 471 | 472 | 473 | # Numberic Plan Indicator (NPI) - SMPP v3.4, section 5.2.6, table 5-4, page 118 474 | 475 | maps['addr_npi_by_name'] = { 476 | 'unknown' :'00', 477 | 'ISDN' :'01', 478 | 'data' :'03', 479 | 'telex' :'04', 480 | 'land_mobile':'06', 481 | 'national' :'08', 482 | 'private' :'09', 483 | 'ERMES' :'0a', 484 | 'internet' :'0e', 485 | 'WAP' :'12' 486 | } 487 | 488 | maps['addr_npi_by_hex'] = { 489 | '00':'unknown', 490 | '01':'ISDN', 491 | '03':'data', 492 | '04':'telex', 493 | '06':'land_mobile', 494 | '08':'national', 495 | '09':'private', 496 | '0a':'ERMES', 497 | '0e':'internet', 498 | '12':'WAP' 499 | } 500 | 501 | 502 | # ESM Class bits - SMPP v3.4, section 5.2.12, page 121 503 | 504 | maps['esm_class_bits'] = { 505 | 'mode_mask' :'03', 506 | 'type_mask' :'3c', 507 | 'feature_mask' :'c0', 508 | 509 | 'mode_default' :'00', 510 | 'mode_datagram' :'01', 511 | 'mode_forward' :'02', 512 | 'mode_store_and_forward' :'03', 513 | 514 | 'type_default' :'00', 515 | 'type_delivery_receipt' :'04', 516 | 'type_delivery_ack' :'08', 517 | 'type_0011' :'0a', 518 | 'type_user_ack' :'10', 519 | 'type_0101' :'14', 520 | 'type_conversation_abort' :'18', 521 | 'type_0111' :'1a', 522 | 'type_intermed_deliv_notif' :'20', 523 | 'type_1001' :'24', 524 | 'type_1010' :'28', 525 | 'type_1011' :'2a', 526 | 'type_1100' :'30', 527 | 'type_1101' :'34', 528 | 'type_1110' :'38', 529 | 'type_1111' :'3a', 530 | 531 | 'feature_none' :'00', 532 | 'feature_UDHI' :'40', 533 | 'feature_reply_path' :'80', 534 | 'feature_UDHI_and_reply_path':'c0' 535 | } 536 | 537 | 538 | # Registered Delivery bits - SMPP v3.4, section 5.2.17, page 124 539 | 540 | maps['registered_delivery_bits'] = { 541 | 'receipt_mask' :'03', 542 | 'ack_mask' :'0c', 543 | 'intermed_notif_mask' :'80', 544 | 545 | 'receipt_none' :'00', 546 | 'receipt_always' :'01', 547 | 'receipt_on_fail' :'02', 548 | 'receipt_res' :'03', 549 | 550 | 'ack_none' :'00', 551 | 'ack_delivery' :'04', 552 | 'ack_user' :'08', 553 | 'ack_delivery_and_user':'0c', 554 | 555 | 'intermed_notif_none' :'00', 556 | 'intermed_notif' :'10' 557 | } 558 | 559 | 560 | # submit_multi dest_flag constants - SMPP v3.4, section 5.2.25, page 129 561 | #maps['dest_flag_by_name'] = { 562 | #'SME Address' :1, 563 | #'Distribution List Name':2 564 | #} 565 | 566 | 567 | # Message State codes returned in query_sm_resp PDUs - SMPP v3.4, section 5.2.28, table 5-6, page 130 568 | maps['message_state_by_name'] = { 569 | 'ENROUTE' :1, 570 | 'DELIVERED' :2, 571 | 'EXPIRED' :3, 572 | 'DELETED' :4, 573 | 'UNDELIVERABLE':5, 574 | 'ACCEPTED' :6, 575 | 'UNKNOWN' :7, 576 | 'REJECTED' :8 577 | } 578 | 579 | 580 | # Facility Code bits for SMPP v4 581 | 582 | maps['facility_code_bits'] = { 583 | 'GF_PVCY' :'00000001', 584 | 'GF_SUBADDR':'00000002', 585 | 'NF_CC' :'00080000', 586 | 'NF_PDC' :'00010000', 587 | 'NF_IS136' :'00020000', 588 | 'NF_IS95A' :'00040000' 589 | } 590 | 591 | 592 | # Optional Parameter Tags - SMPP v3.4, section 5.3.2, Table 5-7, page 132-133 593 | 594 | optional_parameter_tag_by_hex = { 595 | '0005':{'hex':'0005', 'name':'dest_addr_subunit', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.1, page 134 596 | '0006':{'hex':'0006', 'name':'dest_network_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.3, page 135 597 | '0007':{'hex':'0007', 'name':'dest_bearer_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.5, page 136 598 | '0008':{'hex':'0008', 'name':'dest_telematics_id', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.7, page 137 599 | 600 | '000d':{'hex':'000d', 'name':'source_addr_subunit', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.2, page 134 601 | '000e':{'hex':'000e', 'name':'source_network_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.4, page 135 602 | '000f':{'hex':'000f', 'name':'source_bearer_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.6, page 136 603 | '0010':{'hex':'0010', 'name':'source_telematics_id', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.8, page 137 604 | 605 | '0017':{'hex':'0017', 'name':'qos_time_to_live', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.9, page 138 606 | '0019':{'hex':'0019', 'name':'payload_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.10, page 138 607 | 608 | '001d':{'hex':'001d', 'name':'additional_status_info_text', 'type':'string', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.11, page 139 609 | '001e':{'hex':'001e', 'name':'receipted_message_id', 'type':'string', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.12, page 139 610 | 611 | '0030':{'hex':'0030', 'name':'ms_msg_wait_facilities', 'type':'bitmask', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.13, page 140 612 | 613 | '0101':{'hex':'0101', 'name':'PVCY_AuthenticationStr', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 58-62 614 | 615 | '0201':{'hex':'0201', 'name':'privacy_indicator', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.14, page 141 616 | '0202':{'hex':'0202', 'name':'source_subaddress', 'type':'hex', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.15, page 142 617 | '0203':{'hex':'0203', 'name':'dest_subaddress', 'type':'hex', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.16, page 143 618 | '0204':{'hex':'0204', 'name':'user_message_reference', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.17, page 143 619 | '0205':{'hex':'0205', 'name':'user_response_code', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.18, page 144 620 | 621 | '020a':{'hex':'020a', 'name':'source_port', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.20, page 145 622 | '020b':{'hex':'020b', 'name':'destination_port', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.21, page 145 623 | '020c':{'hex':'020c', 'name':'sar_msg_ref_num', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.22, page 146 624 | '020d':{'hex':'020d', 'name':'language_indicator', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.19, page 144 625 | '020e':{'hex':'020e', 'name':'sar_total_segments', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.23, page 147 626 | '020f':{'hex':'020f', 'name':'sar_segment_seqnum', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.24, page 147 627 | '0210':{'hex':'0210', 'name':'sc_interface_version', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.25, page 148 628 | 629 | '0301':{'hex':'0301', 'name':'CC_CBN', 'type':None, 'tech':'V4'}, # v4 page 70 630 | '0302':{'hex':'0302', 'name':'callback_num_pres_ind', 'type':'bitmask', 'tech':'TDMA'}, # SMPP v3.4, section 5.3.2.37, page 156 631 | '0303':{'hex':'0303', 'name':'callback_num_atag', 'type':'hex', 'tech':'TDMA'}, # SMPP v3.4, section 5.3.2.38, page 157 632 | '0304':{'hex':'0304', 'name':'number_of_messages', 'type':'integer', 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.39, page 158 633 | '0381':{'hex':'0381', 'name':'callback_num', 'type':'hex', 'tech':'CDMA, TDMA, GSM, iDEN'}, # SMPP v3.4, section 5.3.2.36, page 155 634 | 635 | '0420':{'hex':'0420', 'name':'dpf_result', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.28, page 149 636 | '0421':{'hex':'0421', 'name':'set_dpf', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.29, page 150 637 | '0422':{'hex':'0422', 'name':'ms_availability_status', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.30, page 151 638 | '0423':{'hex':'0423', 'name':'network_error_code', 'type':'hex', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.31, page 152 639 | '0424':{'hex':'0424', 'name':'message_payload', 'type':'hex', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.32, page 153 640 | '0425':{'hex':'0425', 'name':'delivery_failure_reason', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.33, page 153 641 | '0426':{'hex':'0426', 'name':'more_messages_to_send', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.34, page 154 642 | '0427':{'hex':'0427', 'name':'message_state', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.35, page 154 643 | '0428':{'hex':'0428', 'name':'congestion_state', 'type':None, 'tech':'Generic'}, 644 | 645 | '0501':{'hex':'0501', 'name':'ussd_service_op', 'type':'hex', 'tech':'GSM (USSD)'}, # SMPP v3.4, section 5.3.2.44, page 161 646 | 647 | '0600':{'hex':'0600', 'name':'broadcast_channel_indicator', 'type':None, 'tech':'GSM'}, 648 | '0601':{'hex':'0601', 'name':'broadcast_content_type', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 649 | '0602':{'hex':'0602', 'name':'broadcast_content_type_info', 'type':None, 'tech':'CDMA, TDMA'}, 650 | '0603':{'hex':'0603', 'name':'broadcast_message_class', 'type':None, 'tech':'GSM'}, 651 | '0604':{'hex':'0604', 'name':'broadcast_rep_num', 'type':None, 'tech':'GSM'}, 652 | '0605':{'hex':'0605', 'name':'broadcast_frequency_interval', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 653 | '0606':{'hex':'0606', 'name':'broadcast_area_identifier', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 654 | '0607':{'hex':'0607', 'name':'broadcast_error_status', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 655 | '0608':{'hex':'0608', 'name':'broadcast_area_success', 'type':None, 'tech':'GSM'}, 656 | '0609':{'hex':'0609', 'name':'broadcast_end_time', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 657 | '060a':{'hex':'060a', 'name':'broadcast_service_group', 'type':None, 'tech':'CDMA, TDMA'}, 658 | '060b':{'hex':'060b', 'name':'billing_identification', 'type':None, 'tech':'Generic'}, 659 | 660 | '060d':{'hex':'060d', 'name':'source_network_id', 'type':None, 'tech':'Generic'}, 661 | '060e':{'hex':'060e', 'name':'dest_network_id', 'type':None, 'tech':'Generic'}, 662 | '060f':{'hex':'060f', 'name':'source_node_id', 'type':None, 'tech':'Generic'}, 663 | '0610':{'hex':'0610', 'name':'dest_node_id', 'type':None, 'tech':'Generic'}, 664 | '0611':{'hex':'0611', 'name':'dest_addr_np_resolution', 'type':None, 'tech':'CDMA, TDMA (US Only)'}, 665 | '0612':{'hex':'0612', 'name':'dest_addr_np_information', 'type':None, 'tech':'CDMA, TDMA (US Only)'}, 666 | '0613':{'hex':'0613', 'name':'dest_addr_np_country', 'type':None, 'tech':'CDMA, TDMA (US Only)'}, 667 | 668 | '1101':{'hex':'1101', 'name':'PDC_MessageClass', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 75 669 | '1102':{'hex':'1102', 'name':'PDC_PresentationOption', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 76 670 | '1103':{'hex':'1103', 'name':'PDC_AlertMechanism', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 76 671 | '1104':{'hex':'1104', 'name':'PDC_Teleservice', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 77 672 | '1105':{'hex':'1105', 'name':'PDC_MultiPartMessage', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 77 673 | '1106':{'hex':'1106', 'name':'PDC_PredefinedMsg', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 78 674 | 675 | '1201':{'hex':'1201', 'name':'display_time', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.26, page 148 676 | 677 | '1203':{'hex':'1203', 'name':'sms_signal', 'type':'integer', 'tech':'TDMA'}, # SMPP v3.4, section 5.3.2.40, page 158 678 | '1204':{'hex':'1204', 'name':'ms_validity', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.27, page 149 679 | 680 | '1304':{'hex':'1304', 'name':'IS95A_AlertOnDelivery', 'type':None, 'tech':'CDMA'}, # v4 page 85 681 | '1306':{'hex':'1306', 'name':'IS95A_LanguageIndicator', 'type':None, 'tech':'CDMA'}, # v4 page 86 682 | 683 | '130c':{'hex':'130c', 'name':'alert_on_message_delivery', 'type':None, 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.41, page 159 684 | 685 | '1380':{'hex':'1380', 'name':'its_reply_type', 'type':'integer', 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.42, page 159 686 | '1383':{'hex':'1383', 'name':'its_session_info', 'type':'hex', 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.43, page 160 687 | 688 | '1402':{'hex':'1402', 'name':'operator_id', 'type':None, 'tech':'vendor extension'}, 689 | '1403':{'hex':'1403', 'name':'tariff', 'type':None, 'tech':'Mobile Network Code vendor extension'}, 690 | '1450':{'hex':'1450', 'name':'mcc', 'type':None, 'tech':'Mobile Country Code vendor extension'}, 691 | '1451':{'hex':'1451', 'name':'mnc', 'type':None, 'tech':'Mobile Network Code vendor extension'} 692 | } 693 | def optional_parameter_tag_name_by_hex(x): 694 | return optional_parameter_tag_by_hex.get(x,{}).get('name') 695 | 696 | def optional_parameter_tag_type_by_hex(x): 697 | return optional_parameter_tag_by_hex.get(x,{}).get('type') 698 | 699 | 700 | optional_parameter_tag_by_name = { 701 | 'dest_addr_subunit' :{'hex':'0005', 'name':'dest_addr_subunit', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.1, page 134 702 | 'dest_network_type' :{'hex':'0006', 'name':'dest_network_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.3, page 135 703 | 'dest_bearer_type' :{'hex':'0007', 'name':'dest_bearer_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.5, page 136 704 | 'dest_telematics_id' :{'hex':'0008', 'name':'dest_telematics_id', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.7, page 137 705 | 706 | 'source_addr_subunit' :{'hex':'000d', 'name':'source_addr_subunit', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.2, page 134 707 | 'source_network_type' :{'hex':'000e', 'name':'source_network_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.4, page 135 708 | 'source_bearer_type' :{'hex':'000f', 'name':'source_bearer_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.6, page 136 709 | 'source_telematics_id' :{'hex':'0010', 'name':'source_telematics_id', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.8, page 137 710 | 711 | 'qos_time_to_live' :{'hex':'0017', 'name':'qos_time_to_live', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.9, page 138 712 | 'payload_type' :{'hex':'0019', 'name':'payload_type', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.10, page 138 713 | 714 | 'additional_status_info_text' :{'hex':'001d', 'name':'additional_status_info_text', 'type':'string', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.11, page 139 715 | 'receipted_message_id' :{'hex':'001e', 'name':'receipted_message_id', 'type':'string', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.12, page 139 716 | 717 | 'ms_msg_wait_facilities' :{'hex':'0030', 'name':'ms_msg_wait_facilities', 'type':'bitmask', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.13, page 140 718 | 719 | 'PVCY_AuthenticationStr' :{'hex':'0101', 'name':'PVCY_AuthenticationStr', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 58-62 720 | 721 | 'privacy_indicator' :{'hex':'0201', 'name':'privacy_indicator', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.14, page 141 722 | 'source_subaddress' :{'hex':'0202', 'name':'source_subaddress', 'type':'hex', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.15, page 142 723 | 'dest_subaddress' :{'hex':'0203', 'name':'dest_subaddress', 'type':'hex', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.16, page 143 724 | 'user_message_reference' :{'hex':'0204', 'name':'user_message_reference', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.17, page 143 725 | 'user_response_code' :{'hex':'0205', 'name':'user_response_code', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.18, page 144 726 | 727 | 'source_port' :{'hex':'020a', 'name':'source_port', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.20, page 145 728 | 'destination_port' :{'hex':'020b', 'name':'destination_port', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.21, page 145 729 | 'sar_msg_ref_num' :{'hex':'020c', 'name':'sar_msg_ref_num', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.22, page 146 730 | 'language_indicator' :{'hex':'020d', 'name':'language_indicator', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.19, page 144 731 | 'sar_total_segments' :{'hex':'020e', 'name':'sar_total_segments', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.23, page 147 732 | 'sar_segment_seqnum' :{'hex':'020f', 'name':'sar_segment_seqnum', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.24, page 147 733 | 'sc_interface_version' :{'hex':'0210', 'name':'sc_interface_version', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.25, page 148 734 | 735 | 'CC_CBN' :{'hex':'0301', 'name':'CC_CBN', 'type':None, 'tech':'V4'}, # v4 page 70 736 | 'callback_num_pres_ind' :{'hex':'0302', 'name':'callback_num_pres_ind', 'type':'bitmask', 'tech':'TDMA'}, # SMPP v3.4, section 5.3.2.37, page 156 737 | 'callback_num_atag' :{'hex':'0303', 'name':'callback_num_atag', 'type':'hex', 'tech':'TDMA'}, # SMPP v3.4, section 5.3.2.38, page 157 738 | 'number_of_messages' :{'hex':'0304', 'name':'number_of_messages', 'type':'integer', 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.39, page 158 739 | 'callback_num' :{'hex':'0381', 'name':'callback_num', 'type':'hex', 'tech':'CDMA, TDMA, GSM, iDEN'}, # SMPP v3.4, section 5.3.2.36, page 155 740 | 741 | 'dpf_result' :{'hex':'0420', 'name':'dpf_result', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.28, page 149 742 | 'set_dpf' :{'hex':'0421', 'name':'set_dpf', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.29, page 150 743 | 'ms_availability_status' :{'hex':'0422', 'name':'ms_availability_status', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.30, page 151 744 | 'network_error_code' :{'hex':'0423', 'name':'network_error_code', 'type':'hex', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.31, page 152 745 | 'message_payload' :{'hex':'0424', 'name':'message_payload', 'type':'hex', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.32, page 153 746 | 'delivery_failure_reason' :{'hex':'0425', 'name':'delivery_failure_reason', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.33, page 153 747 | 'more_messages_to_send' :{'hex':'0426', 'name':'more_messages_to_send', 'type':'integer', 'tech':'GSM'}, # SMPP v3.4, section 5.3.2.34, page 154 748 | 'message_state' :{'hex':'0427', 'name':'message_state', 'type':'integer', 'tech':'Generic'}, # SMPP v3.4, section 5.3.2.35, page 154 749 | 'congestion_state' :{'hex':'0428', 'name':'congestion_state', 'type':None, 'tech':'Generic'}, 750 | 751 | 'ussd_service_op' :{'hex':'0501', 'name':'ussd_service_op', 'type':'hex', 'tech':'GSM (USSD)'}, # SMPP v3.4, section 5.3.2.44, page 161 752 | 753 | 'broadcast_channel_indicator' :{'hex':'0600', 'name':'broadcast_channel_indicator', 'type':None, 'tech':'GSM'}, 754 | 'broadcast_content_type' :{'hex':'0601', 'name':'broadcast_content_type', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 755 | 'broadcast_content_type_info' :{'hex':'0602', 'name':'broadcast_content_type_info', 'type':None, 'tech':'CDMA, TDMA'}, 756 | 'broadcast_message_class' :{'hex':'0603', 'name':'broadcast_message_class', 'type':None, 'tech':'GSM'}, 757 | 'broadcast_rep_num' :{'hex':'0604', 'name':'broadcast_rep_num', 'type':None, 'tech':'GSM'}, 758 | 'broadcast_frequency_interval':{'hex':'0605', 'name':'broadcast_frequency_interval', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 759 | 'broadcast_area_identifier' :{'hex':'0606', 'name':'broadcast_area_identifier', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 760 | 'broadcast_error_status' :{'hex':'0607', 'name':'broadcast_error_status', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 761 | 'broadcast_area_success' :{'hex':'0608', 'name':'broadcast_area_success', 'type':None, 'tech':'GSM'}, 762 | 'broadcast_end_time' :{'hex':'0609', 'name':'broadcast_end_time', 'type':None, 'tech':'CDMA, TDMA, GSM'}, 763 | 'broadcast_service_group' :{'hex':'060a', 'name':'broadcast_service_group', 'type':None, 'tech':'CDMA, TDMA'}, 764 | 'billing_identification' :{'hex':'060b', 'name':'billing_identification', 'type':None, 'tech':'Generic'}, 765 | 766 | 'source_network_id' :{'hex':'060d', 'name':'source_network_id', 'type':None, 'tech':'Generic'}, 767 | 'dest_network_id' :{'hex':'060e', 'name':'dest_network_id', 'type':None, 'tech':'Generic'}, 768 | 'source_node_id' :{'hex':'060f', 'name':'source_node_id', 'type':None, 'tech':'Generic'}, 769 | 'dest_node_id' :{'hex':'0610', 'name':'dest_node_id', 'type':None, 'tech':'Generic'}, 770 | 'dest_addr_np_resolution' :{'hex':'0611', 'name':'dest_addr_np_resolution', 'type':None, 'tech':'CDMA, TDMA (US Only)'}, 771 | 'dest_addr_np_information' :{'hex':'0612', 'name':'dest_addr_np_information', 'type':None, 'tech':'CDMA, TDMA (US Only)'}, 772 | 'dest_addr_np_country' :{'hex':'0613', 'name':'dest_addr_np_country', 'type':None, 'tech':'CDMA, TDMA (US Only)'}, 773 | 774 | 'PDC_MessageClass' :{'hex':'1101', 'name':'PDC_MessageClass', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 75 775 | 'PDC_PresentationOption' :{'hex':'1102', 'name':'PDC_PresentationOption', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 76 776 | 'PDC_AlertMechanism' :{'hex':'1103', 'name':'PDC_AlertMechanism', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 76 777 | 'PDC_Teleservice' :{'hex':'1104', 'name':'PDC_Teleservice', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 77 778 | 'PDC_MultiPartMessage' :{'hex':'1105', 'name':'PDC_MultiPartMessage', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 77 779 | 'PDC_PredefinedMsg' :{'hex':'1106', 'name':'PDC_PredefinedMsg', 'type':None, 'tech':'? (J-Phone)'}, # v4 page 78 780 | 781 | 'display_time' :{'hex':'1201', 'name':'display_time', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.26, page 148 782 | 783 | 'sms_signal' :{'hex':'1203', 'name':'sms_signal', 'type':'integer', 'tech':'TDMA'}, # SMPP v3.4, section 5.3.2.40, page 158 784 | 'ms_validity' :{'hex':'1204', 'name':'ms_validity', 'type':'integer', 'tech':'CDMA, TDMA'}, # SMPP v3.4, section 5.3.2.27, page 149 785 | 786 | 'IS95A_AlertOnDelivery' :{'hex':'1304', 'name':'IS95A_AlertOnDelivery', 'type':None, 'tech':'CDMA'}, # v4 page 85 787 | 'IS95A_LanguageIndicator' :{'hex':'1306', 'name':'IS95A_LanguageIndicator', 'type':None, 'tech':'CDMA'}, # v4 page 86 788 | 789 | 'alert_on_message_delivery' :{'hex':'130c', 'name':'alert_on_message_delivery', 'type':None, 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.41, page 159 790 | 791 | 'its_reply_type' :{'hex':'1380', 'name':'its_reply_type', 'type':'integer', 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.42, page 159 792 | 'its_session_info' :{'hex':'1383', 'name':'its_session_info', 'type':'hex', 'tech':'CDMA'}, # SMPP v3.4, section 5.3.2.43, page 160 793 | 794 | 'operator_id' :{'hex':'1402', 'name':'operator_id', 'type':None, 'tech':'vendor extension'}, 795 | 'tariff' :{'hex':'1403', 'name':'tariff', 'type':None, 'tech':'Mobile Network Code vendor extension'}, 796 | 'mcc' :{'hex':'1450', 'name':'mcc', 'type':None, 'tech':'Mobile Country Code vendor extension'}, 797 | 'mnc' :{'hex':'1451', 'name':'mnc', 'type':None, 'tech':'Mobile Network Code vendor extension'} 798 | } 799 | def optional_parameter_tag_hex_by_name(n): 800 | return optional_parameter_tag_by_name.get(n,{}).get('hex') 801 | 802 | 803 | #### Decoding functions ####################################################### 804 | 805 | def unpack_pdu(pdu_bin): 806 | return decode_pdu(binascii.b2a_hex(pdu_bin)) 807 | 808 | 809 | def decode_pdu(pdu_hex): 810 | hex_ref = [pdu_hex] 811 | pdu = {} 812 | pdu['header'] = decode_header(hex_ref) 813 | command = pdu['header'].get('command_id', None) 814 | if command != None: 815 | body = decode_body(command, hex_ref) 816 | if len(body) > 0: 817 | pdu['body'] = body 818 | return pdu 819 | 820 | 821 | def decode_header(hex_ref): 822 | pdu_hex = hex_ref[0] 823 | header = {} 824 | (command_length, command_id, command_status, sequence_number, hex_ref[0]) = \ 825 | (pdu_hex[0:8], pdu_hex[8:16], pdu_hex[16:24], pdu_hex[24:32], pdu_hex[32: ]) 826 | length = int(command_length, 16) 827 | command = command_id_name_by_hex(command_id) 828 | status = command_status_name_by_hex(command_status) 829 | sequence = int(sequence_number, 16) 830 | header = {} 831 | header['command_length'] = length 832 | header['command_id'] = command 833 | header['command_status'] = status 834 | header['sequence_number'] = sequence 835 | return header 836 | 837 | 838 | def decode_body(command, hex_ref): 839 | body = {} 840 | if command != None: 841 | fields = mandatory_parameter_list_by_command_name(command) 842 | mandatory = decode_mandatory_parameters(fields, hex_ref) 843 | if len(mandatory) > 0: 844 | body['mandatory_parameters'] = mandatory 845 | optional = decode_optional_parameters(hex_ref) 846 | if len(optional) > 0: 847 | body['optional_parameters'] = optional 848 | return body 849 | 850 | 851 | def decode_mandatory_parameters(fields, hex_ref): 852 | mandatory_parameters = {} 853 | if len(hex_ref[0]) > 1: 854 | for field in fields: 855 | #old = len(hex_ref[0]) 856 | data = '' 857 | octet = '' 858 | count = 0 859 | if field['var'] == True or field['var'] == False: 860 | while (len(hex_ref[0]) > 1 861 | and (count < field['min'] 862 | or (field['var'] == True 863 | and count < field['max']+1 864 | and octet != '00'))): 865 | octet = octpop(hex_ref) 866 | data += octet 867 | count += 1 868 | elif field['type'] in ['string', 'xstring']: 869 | count = mandatory_parameters[field['var']] 870 | if count == 0: 871 | data = None 872 | else: 873 | for i in range(count): 874 | if len(hex_ref[0]) > 1: 875 | data += octpop(hex_ref) 876 | else: 877 | count = mandatory_parameters[field['var']] 878 | if field['map'] != None: 879 | mandatory_parameters[field['name']] = maps[field['map']+'_by_hex'].get(data, None) 880 | if field['map'] == None or mandatory_parameters[field['name']] == None: 881 | mandatory_parameters[field['name']] = decode_hex_type(data, field['type'], count, hex_ref) 882 | #print field['type'], (old - len(hex_ref[0]))/2, repr(data), field['name'], mandatory_parameters[field['name']] 883 | return mandatory_parameters 884 | 885 | 886 | def decode_optional_parameters(hex_ref): 887 | optional_parameters = [] 888 | hex = hex_ref[0] 889 | while len(hex) > 0: 890 | (tag_hex, length_hex, rest) = (hex[0:4], hex[4:8], hex[8: ]) 891 | tag = optional_parameter_tag_name_by_hex(tag_hex) 892 | if tag == None: 893 | tag = tag_hex 894 | length = int(length_hex, 16) 895 | (value_hex, tail) = (rest[0:length*2], rest[length*2: ]) 896 | if len(value_hex) == 0: 897 | value = None 898 | else: 899 | value = decode_hex_type(value_hex, optional_parameter_tag_type_by_hex(tag_hex)) 900 | hex = tail 901 | optional_parameters.append({'tag':tag, 'length':length, 'value':value}) 902 | return optional_parameters 903 | 904 | 905 | def decode_hex_type(hex, type, count=0, hex_ref=['']): 906 | if hex == None: 907 | return hex 908 | elif type == 'integer': 909 | return int(hex, 16) 910 | elif type == 'string': 911 | return re.sub('00','',hex).decode('hex') 912 | elif type == 'xstring': 913 | return hex.decode('hex') 914 | elif (type == 'dest_address' 915 | or type == 'unsuccess_sme'): 916 | list = [] 917 | fields = mandatory_parameter_list_by_command_name(type) 918 | for i in range(count): 919 | item = decode_mandatory_parameters(fields, hex_ref) 920 | if item.get('dest_flag', None) == 1: # 'dest_address' only 921 | subfields = mandatory_parameter_list_by_command_name('sme_dest_address') 922 | rest = decode_mandatory_parameters(subfields, hex_ref) 923 | item.update(rest) 924 | elif item.get('dest_flag', None) == 2: # 'dest_address' only 925 | subfields = mandatory_parameter_list_by_command_name('distribution_list') 926 | rest = decode_mandatory_parameters(subfields, hex_ref) 927 | item.update(rest) 928 | list.append(item) 929 | return list 930 | else: 931 | return hex 932 | 933 | 934 | def octpop(hex_ref): 935 | octet = None 936 | if len(hex_ref[0]) > 1: 937 | (octet, hex_ref[0]) = (hex_ref[0][0:2], hex_ref[0][2: ]) 938 | return octet 939 | 940 | 941 | #### Encoding functions ####################################################### 942 | 943 | def pack_pdu(pdu_obj): 944 | return binascii.a2b_hex(encode_pdu(pdu_obj)) 945 | 946 | 947 | def encode_pdu(pdu_obj): 948 | header = pdu_obj.get('header', {}) 949 | body = pdu_obj.get('body', {}) 950 | mandatory = body.get('mandatory_parameters', {}) 951 | optional = body.get('optional_parameters', []) 952 | body_hex = '' 953 | fields = mandatory_parameter_list_by_command_name(header['command_id']) 954 | body_hex += encode_mandatory_parameters(mandatory, fields) 955 | for opt in optional: 956 | body_hex += encode_optional_parameter(opt['tag'], opt['value']) 957 | actual_length = 16 + len(body_hex)/2 958 | command_length = '%08x' % actual_length 959 | command_id = command_id_hex_by_name(header['command_id']) 960 | command_status = command_status_hex_by_name(header['command_status']) 961 | sequence_number = '%08x' % header['sequence_number'] 962 | pdu_hex = command_length + command_id + command_status + sequence_number + body_hex 963 | return pdu_hex 964 | 965 | 966 | def encode_mandatory_parameters(mandatory_obj, fields): 967 | mandatory_hex_array = [] 968 | index_names = {} 969 | index = 0 970 | for field in fields: 971 | param = mandatory_obj.get(field['name'], None) 972 | param_length = None 973 | if param != None or field['min'] > 0: 974 | map = None 975 | if field['map'] != None: 976 | map = maps.get(field['map']+'_by_name', None) 977 | if isinstance(param, list): 978 | hex_list = [] 979 | for item in param: 980 | flagfields = mandatory_parameter_list_by_command_name(field['type']) 981 | plusfields = [] 982 | if item.get('dest_flag', None) == 1: 983 | plusfields = mandatory_parameter_list_by_command_name('sme_dest_address') 984 | elif item.get('dest_flag', None) == 2: 985 | plusfields = mandatory_parameter_list_by_command_name('distribution_list') 986 | hex_item = encode_mandatory_parameters(item, flagfields + plusfields) 987 | if isinstance(hex_item, str) and len(hex_item) > 0: 988 | hex_list.append(hex_item) 989 | param_length = len(hex_list) 990 | mandatory_hex_array.append(''.join(hex_list)) 991 | else: 992 | hex_param = encode_param_type( 993 | param, field['type'], field['min'], field['max'], map) 994 | param_length = len(hex_param)/2 995 | mandatory_hex_array.append(hex_param) 996 | index_names[field['name']] = index 997 | length_index = index_names.get(field['var'], None) 998 | if length_index != None and param_length != None: 999 | mandatory_hex_array[length_index] = encode_param_type( 1000 | param_length, 1001 | 'integer', 1002 | len(mandatory_hex_array[length_index])/2) 1003 | index += 1 1004 | return ''.join(mandatory_hex_array) 1005 | 1006 | 1007 | def encode_optional_parameter(tag, value): 1008 | optional_hex_array = [] 1009 | tag_hex = optional_parameter_tag_hex_by_name(tag) 1010 | if tag_hex != None: 1011 | value_hex = encode_param_type( 1012 | value, 1013 | optional_parameter_tag_type_by_hex(tag_hex)) 1014 | length_hex = '%04x' % (len(value_hex)/2) 1015 | optional_hex_array.append(tag_hex + length_hex + value_hex) 1016 | return ''.join(optional_hex_array) 1017 | 1018 | 1019 | def encode_param_type(param, type, min=0, max=None, map=None): 1020 | if param == None: 1021 | hex = None 1022 | elif map != None: 1023 | if type == 'integer' and isinstance(param, int): 1024 | hex = ('%0'+str(min*2)+'x') % param 1025 | else: 1026 | hex = map.get(param, ('%0'+str(min*2)+'x') % 0) 1027 | elif type == 'integer': 1028 | hex = ('%0'+str(min*2)+'x') % int(param) 1029 | elif type == 'string': 1030 | hex = param.encode('hex') + '00' 1031 | elif type == 'xstring': 1032 | hex = param.encode('hex') 1033 | elif type == 'bitmask': 1034 | hex = param 1035 | elif type == 'hex': 1036 | hex = param 1037 | else: 1038 | hex = None 1039 | if hex: 1040 | if len(hex) % 2: 1041 | # pad odd length hex strings 1042 | hex = '0' + hex 1043 | #print type, min, max, repr(param), hex, map 1044 | return hex 1045 | 1046 | 1047 | -------------------------------------------------------------------------------- /smpp/pdu_builder.py: -------------------------------------------------------------------------------- 1 | 2 | from pdu import * 3 | 4 | class PDU(object): 5 | def __init__(self, 6 | command_id, 7 | command_status, 8 | sequence_number, 9 | **kwargs): 10 | super(PDU, self).__init__() 11 | self.obj = {} 12 | self.obj['header'] = {} 13 | self.obj['header']['command_length'] = 0 14 | self.obj['header']['command_id'] = command_id 15 | self.obj['header']['command_status'] = command_status 16 | self.obj['header']['sequence_number'] = sequence_number 17 | 18 | 19 | def __add_optional_parameter(self, tag, value): 20 | if self.obj.get('body') == None: 21 | self.obj['body'] = {} 22 | if self.obj['body'].get('optional_parameters') == None: 23 | self.obj['body']['optional_parameters'] = [] 24 | self.obj['body']['optional_parameters'].append({ 25 | 'tag':tag, 26 | 'length':0, 27 | 'value':value, 28 | }) 29 | 30 | def set_sar_msg_ref_num(self, value): 31 | self.__add_optional_parameter('sar_msg_ref_num', value) 32 | 33 | def set_sar_segment_seqnum(self, value): 34 | self.__add_optional_parameter('sar_segment_seqnum', value) 35 | 36 | def set_sar_total_segments(self, value): 37 | self.__add_optional_parameter('sar_total_segments', value) 38 | 39 | 40 | def get_obj(self): 41 | return self.obj 42 | 43 | 44 | def get_hex(self): 45 | return encode_pdu(self.obj) 46 | 47 | 48 | def get_bin(self): 49 | return pack_pdu(self.obj) 50 | 51 | 52 | class Bind(PDU): 53 | def __init__(self, 54 | command_id, 55 | sequence_number, 56 | system_id = '', 57 | password = '', 58 | system_type = '', 59 | interface_version = '34', 60 | addr_ton = 0, 61 | addr_npi = 0, 62 | address_range = '', 63 | **kwargs): 64 | super(Bind, self).__init__( 65 | command_id, 66 | 'ESME_ROK', 67 | sequence_number, 68 | ) 69 | self.obj['body'] = {} 70 | self.obj['body']['mandatory_parameters'] = {} 71 | self.obj['body']['mandatory_parameters']['system_id'] = system_id 72 | self.obj['body']['mandatory_parameters']['password'] = password 73 | self.obj['body']['mandatory_parameters']['system_type'] = system_type 74 | self.obj['body']['mandatory_parameters']['interface_version'] = interface_version 75 | self.obj['body']['mandatory_parameters']['addr_ton'] = addr_ton 76 | self.obj['body']['mandatory_parameters']['addr_npi'] = addr_npi 77 | self.obj['body']['mandatory_parameters']['address_range'] = address_range 78 | 79 | 80 | class BindTransmitter(Bind): 81 | def __init__(self, 82 | sequence_number, 83 | **kwargs): 84 | super(BindTransmitter, self).__init__('bind_transmitter', sequence_number, **kwargs) 85 | 86 | 87 | class BindReceiver(Bind): 88 | def __init__(self, 89 | sequence_number, 90 | **kwargs): 91 | super(BindReceiver, self).__init__('bind_receiver', sequence_number, **kwargs) 92 | 93 | 94 | class BindTransceiver(Bind): 95 | def __init__(self, 96 | sequence_number, 97 | **kwargs): 98 | super(BindTransceiver, self).__init__('bind_transceiver', sequence_number, **kwargs) 99 | 100 | 101 | class BindResp(PDU): 102 | def __init__(self, 103 | command_id, 104 | command_status, 105 | sequence_number, 106 | system_id = '', 107 | **kwargs): 108 | super(BindResp, self).__init__( 109 | command_id, 110 | command_status, 111 | sequence_number, 112 | ) 113 | self.obj['body'] = {} 114 | self.obj['body']['mandatory_parameters'] = {} 115 | self.obj['body']['mandatory_parameters']['system_id'] = system_id 116 | 117 | 118 | class BindTransmitterResp(BindResp): 119 | def __init__(self, 120 | sequence_number, 121 | command_status="ESME_ROK", 122 | **kwargs): 123 | super(BindTransmitterResp, self).__init__('bind_transmitter_resp', command_status, sequence_number, **kwargs) 124 | 125 | 126 | class BindReceiverResp(BindResp): 127 | def __init__(self, 128 | sequence_number, 129 | command_status="ESME_ROK", 130 | **kwargs): 131 | super(BindReceiverResp, self).__init__('bind_receiver_resp', command_status, sequence_number, **kwargs) 132 | 133 | 134 | class BindTransceiverResp(BindResp): 135 | def __init__(self, 136 | sequence_number, 137 | command_status="ESME_ROK", 138 | **kwargs): 139 | super(BindTransceiverResp, self).__init__('bind_transceiver_resp', command_status, sequence_number, **kwargs) 140 | 141 | 142 | class Unbind(PDU): 143 | def __init__(self, 144 | sequence_number, 145 | **kwargs): 146 | super(Unbind, self).__init__('unbind', 'ESME_ROK', sequence_number, **kwargs) 147 | 148 | 149 | class SM1(PDU): 150 | def __init__(self, 151 | command_id, 152 | sequence_number, 153 | service_type = '', 154 | source_addr_ton = 0, 155 | source_addr_npi = 0, 156 | source_addr = '', 157 | esm_class = 0, 158 | protocol_id = 0, 159 | priority_flag = 0, 160 | schedule_delivery_time = '', 161 | validity_period = '', 162 | registered_delivery = 0, 163 | replace_if_present_flag = 0, 164 | data_coding = 0, 165 | sm_default_msg_id = 0, 166 | sm_length = 0, 167 | short_message = None, 168 | **kwargs): 169 | super(SM1, self).__init__( 170 | command_id, 171 | 'ESME_ROK', 172 | sequence_number, 173 | ) 174 | self.obj['body'] = {} 175 | self.obj['body']['mandatory_parameters'] = {} 176 | self.obj['body']['mandatory_parameters']['service_type'] = service_type 177 | self.obj['body']['mandatory_parameters']['source_addr_ton'] = source_addr_ton 178 | self.obj['body']['mandatory_parameters']['source_addr_npi'] = source_addr_npi 179 | self.obj['body']['mandatory_parameters']['source_addr'] = source_addr 180 | self.obj['body']['mandatory_parameters']['esm_class'] = esm_class 181 | self.obj['body']['mandatory_parameters']['protocol_id'] = protocol_id 182 | self.obj['body']['mandatory_parameters']['priority_flag'] = priority_flag 183 | self.obj['body']['mandatory_parameters']['schedule_delivery_time'] = schedule_delivery_time 184 | self.obj['body']['mandatory_parameters']['validity_period'] = validity_period 185 | self.obj['body']['mandatory_parameters']['registered_delivery'] = registered_delivery 186 | self.obj['body']['mandatory_parameters']['replace_if_present_flag'] = replace_if_present_flag 187 | self.obj['body']['mandatory_parameters']['data_coding'] = data_coding 188 | self.obj['body']['mandatory_parameters']['sm_default_msg_id'] = sm_default_msg_id 189 | self.obj['body']['mandatory_parameters']['sm_length'] = sm_length 190 | self.obj['body']['mandatory_parameters']['short_message'] = short_message 191 | 192 | 193 | def add_message_payload(self, value): 194 | self.obj['body']['mandatory_parameters']['sm_length'] = 0 195 | self.obj['body']['mandatory_parameters']['short_message'] = None 196 | self._PDU__add_optional_parameter('message_payload', value) 197 | 198 | 199 | class SubmitMulti(SM1): 200 | def __init__(self, 201 | sequence_number, 202 | number_of_dests = 0, 203 | dest_address = [], 204 | **kwargs): 205 | super(SubmitMulti, self).__init__('submit_multi', sequence_number, **kwargs) 206 | mandatory_parameters = self.obj['body']['mandatory_parameters'] 207 | mandatory_parameters['number_of_dests'] = number_of_dests 208 | mandatory_parameters['dest_address'] = [] + dest_address 209 | 210 | def addDestinationAddress(self, 211 | destination_addr, 212 | dest_addr_ton = 0, 213 | dest_addr_npi = 0, 214 | ): 215 | if isinstance(destination_addr, str) and len(destination_addr) > 0: 216 | new_entry = { 217 | 'dest_flag':1, 218 | 'dest_addr_ton':dest_addr_ton, 219 | 'dest_addr_npi':dest_addr_npi, 220 | 'destination_addr':destination_addr, 221 | } 222 | mandatory_parameters = self.obj['body']['mandatory_parameters'] 223 | mandatory_parameters['dest_address'].append(new_entry) 224 | mandatory_parameters['number_of_dests'] = len( 225 | mandatory_parameters['dest_address']) 226 | return True 227 | else: 228 | return False 229 | 230 | def addDistributionList(self, 231 | dl_name, 232 | ): 233 | if isinstance(dl_name, str) and len(dl_name) > 0: 234 | new_entry = { 235 | 'dest_flag':2, 236 | 'dl_name':dl_name, 237 | } 238 | mandatory_parameters = self.obj['body']['mandatory_parameters'] 239 | mandatory_parameters['dest_address'].append(new_entry) 240 | mandatory_parameters['number_of_dests'] = len( 241 | mandatory_parameters['dest_address']) 242 | return True 243 | else: 244 | return False 245 | 246 | 247 | class SM2(SM1): 248 | def __init__(self, 249 | command_id, 250 | sequence_number, 251 | dest_addr_ton = 0, 252 | dest_addr_npi = 0, 253 | destination_addr = '', 254 | **kwargs): 255 | super(SM2, self).__init__(command_id, sequence_number, **kwargs) 256 | mandatory_parameters = self.obj['body']['mandatory_parameters'] 257 | mandatory_parameters['dest_addr_ton'] = dest_addr_ton 258 | mandatory_parameters['dest_addr_npi'] = dest_addr_npi 259 | mandatory_parameters['destination_addr'] = destination_addr 260 | 261 | 262 | class SubmitSM(SM2): 263 | def __init__(self, 264 | sequence_number, 265 | **kwargs): 266 | super(SubmitSM, self).__init__('submit_sm', sequence_number, **kwargs) 267 | 268 | 269 | class SubmitSMResp(PDU): 270 | def __init__(self, 271 | sequence_number, 272 | message_id, 273 | command_status = 'ESME_ROK', 274 | **kwargs): 275 | super(SubmitSMResp, self).__init__( 276 | 'submit_sm_resp', 277 | command_status, 278 | sequence_number, 279 | **kwargs) 280 | self.obj['body'] = {} 281 | self.obj['body']['mandatory_parameters'] = {} 282 | self.obj['body']['mandatory_parameters']['message_id'] = message_id 283 | 284 | 285 | class DeliverSM(SM2): 286 | def __init__(self, 287 | sequence_number, 288 | **kwargs): 289 | super(DeliverSM, self).__init__('deliver_sm',sequence_number, **kwargs) 290 | 291 | 292 | class DeliverSMResp(PDU): 293 | def __init__(self, 294 | sequence_number, 295 | message_id = '', 296 | command_status = 'ESME_ROK', 297 | **kwargs): 298 | super(DeliverSMResp, self).__init__( 299 | 'deliver_sm_resp', 300 | command_status, 301 | sequence_number, 302 | **kwargs) 303 | self.obj['body'] = {} 304 | self.obj['body']['mandatory_parameters'] = {} 305 | self.obj['body']['mandatory_parameters']['message_id'] = message_id 306 | 307 | 308 | class EnquireLink(PDU): 309 | def __init__(self, 310 | sequence_number, 311 | **kwargs): 312 | super(EnquireLink, self).__init__( 313 | 'enquire_link', 314 | 'ESME_ROK', 315 | sequence_number, 316 | **kwargs) 317 | 318 | 319 | class EnquireLinkResp(PDU): 320 | def __init__(self, 321 | sequence_number, 322 | **kwargs): 323 | super(EnquireLinkResp, self).__init__( 324 | 'enquire_link_resp', 325 | 'ESME_ROK', 326 | sequence_number, 327 | **kwargs) 328 | 329 | 330 | class QuerySM(PDU): 331 | def __init__(self, 332 | sequence_number, 333 | message_id, 334 | source_addr = '', 335 | source_addr_ton = 0, 336 | source_addr_npi = 0, 337 | **kwargs): 338 | super(QuerySM, self).__init__( 339 | 'query_sm', 340 | 'ESME_ROK', 341 | sequence_number, 342 | **kwargs) 343 | self.obj['body'] = {} 344 | self.obj['body']['mandatory_parameters'] = {} 345 | self.obj['body']['mandatory_parameters']['message_id'] = message_id 346 | self.obj['body']['mandatory_parameters']['source_addr'] = source_addr 347 | self.obj['body']['mandatory_parameters']['source_addr_ton'] = source_addr_ton 348 | self.obj['body']['mandatory_parameters']['source_addr_npi'] = source_addr_npi 349 | 350 | 351 | #bind = BindTransmitter(system_id='test_id', password='abc123') 352 | #print bind.get_obj() 353 | #print bind.get_hex() 354 | #print bind.get_bin() 355 | ##print json.dumps(bind.get_obj(), indent=4, sort_keys=True) 356 | ##print json.dumps(decode_pdu(bind.get_hex()), indent=4, sort_keys=True) 357 | #print json.dumps(unpack_pdu(bind.get_bin()), indent=4, sort_keys=True) 358 | 359 | #sm = SubmitSM(short_message='testing testing') 360 | #print json.dumps(unpack_pdu(sm.get_bin()), indent=4, sort_keys=True) 361 | #sm.add_message_payload('616263646566676869') 362 | #print json.dumps(unpack_pdu(sm.get_bin()), indent=4, sort_keys=True) 363 | -------------------------------------------------------------------------------- /smpp/pdu_inspector.py: -------------------------------------------------------------------------------- 1 | 2 | from pdu import * 3 | 4 | 5 | def detect_multipart(pdu): 6 | to_msisdn = pdu['body']['mandatory_parameters']['destination_addr'] 7 | from_msisdn = pdu['body']['mandatory_parameters']['source_addr'] 8 | short_message = pdu['body']['mandatory_parameters']['short_message'] 9 | optional_parameters = {} 10 | for d in pdu['body'].get('optional_parameters',[]): 11 | optional_parameters[d['tag']] = d['value'] 12 | 13 | #print repr(pdu) 14 | 15 | try: 16 | mdict = {'multipart_type':'TLV'} 17 | mdict['to_msisdn'] = to_msisdn 18 | mdict['from_msisdn'] = from_msisdn 19 | mdict['reference_number'] = optional_parameters['sar_msg_ref_num'] 20 | mdict['total_number'] = optional_parameters['sar_total_segments'] 21 | mdict['part_number'] = optional_parameters['sar_segment_seqnum'] 22 | mdict['part_message'] = short_message 23 | return mdict 24 | except: 25 | pass 26 | 27 | # all other multipart types will fail on short_message == None 28 | if short_message == None: 29 | return None 30 | 31 | if (short_message[0:1] == '\x00' 32 | and short_message[1:2] == '\x03' 33 | and len(short_message) >= 5): 34 | mdict = {'multipart_type':'SAR'} 35 | mdict['to_msisdn'] = to_msisdn 36 | mdict['from_msisdn'] = from_msisdn 37 | mdict['reference_number'] = int(binascii.b2a_hex(short_message[2:3]), 16) 38 | mdict['total_number'] = int(binascii.b2a_hex(short_message[3:4]), 16) 39 | mdict['part_number'] = int(binascii.b2a_hex(short_message[4:5]), 16) 40 | mdict['part_message'] = short_message[5:] 41 | return mdict 42 | 43 | if (short_message[0:1] == '\x05' 44 | and short_message[1:2] == '\x00' 45 | and short_message[2:3] == '\x03' 46 | and len(short_message) >= 6): 47 | mdict = {'multipart_type':'CSM'} 48 | mdict['to_msisdn'] = to_msisdn 49 | mdict['from_msisdn'] = from_msisdn 50 | mdict['reference_number'] = int(binascii.b2a_hex(short_message[3:4]), 16) 51 | mdict['total_number'] = int(binascii.b2a_hex(short_message[4:5]), 16) 52 | mdict['part_number'] = int(binascii.b2a_hex(short_message[5:6]), 16) 53 | mdict['part_message'] = short_message[6:] 54 | return mdict 55 | 56 | if (short_message[0:1] == '\x06' 57 | and short_message[1:2] == '\x00' 58 | and short_message[2:3] == '\x04' 59 | and len(short_message) >= 7): 60 | mdict = {'multipart_type':'CSM16'} 61 | mdict['to_msisdn'] = to_msisdn 62 | mdict['from_msisdn'] = from_msisdn 63 | mdict['reference_number'] = int(binascii.b2a_hex(short_message[3:5]), 16) 64 | mdict['total_number'] = int(binascii.b2a_hex(short_message[5:6]), 16) 65 | mdict['part_number'] = int(binascii.b2a_hex(short_message[6:7]), 16) 66 | mdict['part_message'] = short_message[7:] 67 | return mdict 68 | 69 | return None 70 | 71 | 72 | def multipart_key(multipart, delimiter='_'): 73 | key_list = [] 74 | key_list.append(str(multipart.get('from_msisdn'))) 75 | key_list.append(str(multipart.get('to_msisdn'))) 76 | key_list.append(str(multipart.get('reference_number'))) 77 | key_list.append(str(multipart.get('total_number'))) 78 | return delimiter.join(key_list) 79 | 80 | 81 | class MultipartMessage: 82 | 83 | def __init__(self, array=None): 84 | self.array = {} 85 | for k,v in (array or {}).items(): 86 | self.array.update({int(k):v}) 87 | 88 | def add_pdu(self, pdu): 89 | part = detect_multipart(pdu) 90 | if part: 91 | self.array[part['part_number']] = part 92 | return True 93 | else: 94 | return False 95 | 96 | def get_partial(self): 97 | items = self.array.items() 98 | message = ''.join([i[1]['part_message'] for i in items]) 99 | to_msisdn = from_msisdn = '' 100 | if len(items): 101 | to_msisdn = items[0][1].get('to_msisdn') 102 | from_msisdn = items[0][1].get('from_msisdn') 103 | return {'to_msisdn':to_msisdn, 104 | 'from_msisdn':from_msisdn, 105 | 'message':message} 106 | 107 | def get_completed(self): 108 | items = self.array.items() 109 | if len(items) and len(items) == items[0][1].get('total_number'): 110 | return self.get_partial() 111 | return None 112 | 113 | def get_key(self, delimiter = '_'): 114 | items = self.array.items() 115 | if len(items): 116 | return multipart_key(items[0][1], delimiter) 117 | return None 118 | 119 | def get_array(self): 120 | return self.array 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /smpp/smsc.py: -------------------------------------------------------------------------------- 1 | import socket 2 | 3 | from esme import * 4 | 5 | 6 | class SMSC(ESME): # this is a dummy SMSC, just for testing 7 | 8 | def __init__(self, port=2775, credentials={}): 9 | self.credentials = credentials 10 | self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | self.server.bind(('', port)) 12 | self.server.listen(1) 13 | self.conn, self.addr = self.server.accept() 14 | print 'Connected by', self.addr 15 | while 1: 16 | pdu = self._ESME__recv() 17 | if not pdu: break 18 | self.conn.send(pack_pdu(self.__response(pdu))) 19 | self.conn.close() 20 | 21 | 22 | def __response(self, pdu): 23 | pdu_resp = {} 24 | resp_header = {} 25 | pdu_resp['header'] = resp_header 26 | resp_header['command_length'] = 0 27 | resp_header['command_id'] = 'generic_nack' 28 | resp_header['command_status'] = 'ESME_ROK' 29 | resp_header['sequence_number'] = pdu['header']['sequence_number'] 30 | if pdu['header']['command_id'] in [ 31 | 'bind_transmitter', 32 | 'bind_receiver', 33 | 'bind_transceiver', 34 | 'unbind', 35 | 'submit_sm', 36 | 'submit_multi', 37 | 'deliver_sm', 38 | 'data_sm', 39 | 'query_sm', 40 | 'cancel_sm', 41 | 'replace_sm', 42 | 'enquire_link', 43 | ]: 44 | resp_header['command_id'] = pdu['header']['command_id']+'_resp' 45 | if pdu['header']['command_id'] in [ 46 | 'bind_transmitter', 47 | 'bind_receiver', 48 | 'bind_transceiver', 49 | ]: 50 | resp_body = {} 51 | pdu_resp['body'] = resp_body 52 | resp_mandatory_parameters = {} 53 | resp_body['mandatory_parameters'] = resp_mandatory_parameters 54 | resp_mandatory_parameters['system_id'] = pdu['body']['mandatory_parameters']['system_id'] 55 | if pdu['header']['command_id'] in [ 56 | #'submit_sm', # message_id is optional in submit_sm 57 | 'submit_multi', 58 | 'deliver_sm', 59 | 'data_sm', 60 | 'query_sm', 61 | ]: 62 | resp_body = {} 63 | pdu_resp['body'] = resp_body 64 | resp_mandatory_parameters = {} 65 | resp_body['mandatory_parameters'] = resp_mandatory_parameters 66 | resp_mandatory_parameters['message_id'] = '' 67 | if pdu['header']['command_id'] == 'submit_multi': 68 | resp_mandatory_parameters['no_unsuccess'] = 0 69 | if pdu['header']['command_id'] == 'query_sm': 70 | resp_mandatory_parameters['final_date'] = '' 71 | resp_mandatory_parameters['message_state'] = 0 72 | resp_mandatory_parameters['error_code'] = 0 73 | return pdu_resp 74 | 75 | 76 | 77 | if __name__ == '__main__': 78 | smsc = SMSC(2777) 79 | 80 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dmaclay/python-smpp/4b37d211ea38f52ae282a469e377baabbcd17199/test/__init__.py -------------------------------------------------------------------------------- /test/pdu.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | pdu_objects = [ 4 | { 5 | 'header': { 6 | 'command_length': 0, 7 | 'command_id': 'bind_transmitter', 8 | 'command_status': 'ESME_ROK', 9 | 'sequence_number': 0, 10 | }, 11 | 'body': { 12 | 'mandatory_parameters': { 13 | 'system_id':'test_system', 14 | 'password':'abc123', 15 | 'system_type':'', 16 | 'interface_version':'34', 17 | 'addr_ton':1, 18 | 'addr_npi':1, 19 | 'address_range':'', 20 | }, 21 | }, 22 | }, 23 | { 24 | 'header': { 25 | 'command_length': 0, 26 | 'command_id': 'bind_transmitter_resp', 27 | 'command_status': 'ESME_ROK', 28 | 'sequence_number': 0, 29 | }, 30 | 'body': { 31 | 'mandatory_parameters': { 32 | 'system_id':'test_system', 33 | }, 34 | }, 35 | }, 36 | { 37 | 'header': { 38 | 'command_length': 0, 39 | 'command_id': 'bind_receiver', 40 | 'command_status': 'ESME_ROK', 41 | 'sequence_number': 0, 42 | }, 43 | 'body': { 44 | 'mandatory_parameters': { 45 | 'system_id':'test_system', 46 | 'password':'abc123', 47 | 'system_type':'', 48 | 'interface_version':'34', 49 | 'addr_ton':1, 50 | 'addr_npi':1, 51 | 'address_range':'', 52 | }, 53 | }, 54 | }, 55 | { 56 | 'header': { 57 | 'command_length': 0, 58 | 'command_id': 'bind_receiver_resp', 59 | 'command_status': 'ESME_ROK', 60 | 'sequence_number': 0, 61 | }, 62 | 'body': { 63 | 'mandatory_parameters': { 64 | 'system_id':'test_system', 65 | }, 66 | }, 67 | }, 68 | { 69 | 'header': { 70 | 'command_length': 0, 71 | 'command_id': 'bind_transceiver', 72 | 'command_status': 'ESME_ROK', 73 | 'sequence_number': 0, 74 | }, 75 | 'body': { 76 | 'mandatory_parameters': { 77 | 'system_id':'test_system', 78 | 'password':'abc123', 79 | 'system_type':'', 80 | 'interface_version':'34', 81 | 'addr_ton':1, 82 | 'addr_npi':1, 83 | 'address_range':'', 84 | }, 85 | }, 86 | }, 87 | { 88 | 'header': { 89 | 'command_length': 0, 90 | 'command_id': 'bind_transceiver_resp', 91 | 'command_status': 'ESME_ROK', 92 | 'sequence_number': 0, 93 | }, 94 | 'body': { 95 | 'mandatory_parameters': { 96 | 'system_id':'test_system', 97 | }, 98 | }, 99 | }, 100 | { 101 | 'header': { 102 | 'command_length': 0, 103 | 'command_id': 'outbind', 104 | 'command_status': 'ESME_ROK', 105 | 'sequence_number': 0, 106 | }, 107 | 'body': { 108 | 'mandatory_parameters': { 109 | 'system_id':'test_system', 110 | 'password':'abc123', 111 | }, 112 | }, 113 | }, 114 | { 115 | 'header': { 116 | 'command_length': 0, 117 | 'command_id': 'unbind', 118 | 'command_status': 'ESME_ROK', 119 | 'sequence_number': 0, 120 | }, 121 | }, 122 | { 123 | 'header': { 124 | 'command_length': 0, 125 | 'command_id': 'unbind_resp', 126 | 'command_status': 'ESME_ROK', 127 | 'sequence_number': 0, 128 | }, 129 | }, 130 | { 131 | 'header': { 132 | 'command_length': 0, 133 | 'command_id': 'generic_nack', 134 | 'command_status': 'ESME_ROK', 135 | 'sequence_number': 0, 136 | }, 137 | }, 138 | { 139 | 'header': { 140 | 'command_length': 0, 141 | 'command_id': 'submit_sm', 142 | 'command_status': 'ESME_ROK', 143 | 'sequence_number': 0, 144 | }, 145 | 'body': { 146 | 'mandatory_parameters': { 147 | 'service_type':'', 148 | 'source_addr_ton':1, 149 | 'source_addr_npi':1, 150 | 'source_addr':'', 151 | 'dest_addr_ton':1, 152 | 'dest_addr_npi':1, 153 | 'destination_addr':'', 154 | 'esm_class':0, 155 | 'protocol_id':0, 156 | 'priority_flag':0, 157 | 'schedule_delivery_time':'', 158 | 'validity_period':'', 159 | 'registered_delivery':0, 160 | 'replace_if_present_flag':0, 161 | 'data_coding':0, 162 | 'sm_default_msg_id':0, 163 | 'sm_length':1, 164 | 'short_message':'testing 123', 165 | }, 166 | }, 167 | }, 168 | { 169 | 'header': { 170 | 'command_length': 0, 171 | 'command_id': 'submit_sm', 172 | 'command_status': 'ESME_ROK', 173 | 'sequence_number': 0, 174 | }, 175 | 'body': { 176 | 'mandatory_parameters': { 177 | 'service_type':'', 178 | 'source_addr_ton':1, 179 | 'source_addr_npi':1, 180 | 'source_addr':'', 181 | 'dest_addr_ton':1, 182 | 'dest_addr_npi':1, 183 | 'destination_addr':'', 184 | 'esm_class':0, 185 | 'protocol_id':0, 186 | 'priority_flag':0, 187 | 'schedule_delivery_time':'', 188 | 'validity_period':'', 189 | 'registered_delivery':0, 190 | 'replace_if_present_flag':0, 191 | 'data_coding':0, 192 | 'sm_default_msg_id':0, 193 | 'sm_length':0, 194 | 'short_message':None, 195 | # 'short_message' can be of zero length 196 | }, 197 | 'optional_parameters': [ 198 | { 199 | 'tag':'message_payload', 200 | 'length':0, 201 | 'value':'5666', 202 | }, 203 | ], 204 | }, 205 | }, 206 | #] 207 | #breaker = [ 208 | { 209 | 'header': { 210 | 'command_length': 0, 211 | 'command_id': 'submit_sm_resp', 212 | 'command_status': 'ESME_ROK', 213 | 'sequence_number': 0, 214 | }, 215 | 'body': { 216 | 'mandatory_parameters': { 217 | 'message_id':'', 218 | }, 219 | }, 220 | }, 221 | { 222 | 'header': { 223 | 'command_length': 0, 224 | 'command_id': 'submit_sm_resp', 225 | 'command_status': 'ESME_RSYSERR', 226 | 'sequence_number': 0, 227 | }, 228 | # submit_sm_resp can have no body for failures 229 | }, 230 | { 231 | 'header': { 232 | 'command_length': 0, 233 | 'command_id': 'submit_multi', 234 | 'command_status': 'ESME_ROK', 235 | 'sequence_number': 0, 236 | }, 237 | 'body': { 238 | 'mandatory_parameters': { 239 | 'service_type':'', 240 | 'source_addr_ton':1, 241 | 'source_addr_npi':1, 242 | 'source_addr':'', 243 | 'number_of_dests':0, 244 | 'dest_address':[ 245 | { 246 | 'dest_flag':1, 247 | 'dest_addr_ton':1, 248 | 'dest_addr_npi':1, 249 | 'destination_addr':'the address' 250 | }, 251 | { 252 | 'dest_flag':2, 253 | 'dl_name':'the list', 254 | }, 255 | { 256 | 'dest_flag':2, 257 | 'dl_name':'the other list', 258 | }, 259 | #{} 260 | ], 261 | 'esm_class':0, 262 | 'protocol_id':0, 263 | 'priority_flag':0, 264 | 'schedule_delivery_time':'', 265 | 'validity_period':'', 266 | 'registered_delivery':0, 267 | 'replace_if_present_flag':0, 268 | 'data_coding':0, 269 | 'sm_default_msg_id':0, 270 | 'sm_length':1, 271 | 'short_message':'testing 123', 272 | }, 273 | }, 274 | }, 275 | { 276 | 'header': { 277 | 'command_length': 0, 278 | 'command_id': 'submit_multi_resp', 279 | 'command_status': 'ESME_ROK', 280 | 'sequence_number': 0, 281 | }, 282 | 'body': { 283 | 'mandatory_parameters': { 284 | 'message_id':'', 285 | 'no_unsuccess':5, 286 | 'unsuccess_sme':[ 287 | { 288 | 'dest_addr_ton':1, 289 | 'dest_addr_npi':1, 290 | 'destination_addr':'', 291 | 'error_status_code':0, 292 | }, 293 | { 294 | 'dest_addr_ton':3, 295 | 'dest_addr_npi':1, 296 | 'destination_addr':'555', 297 | 'error_status_code':0, 298 | }, 299 | ], 300 | }, 301 | }, 302 | }, 303 | #] 304 | #breaker = [ 305 | { 306 | 'header': { 307 | 'command_length': 0, 308 | 'command_id': 'deliver_sm', 309 | 'command_status': 'ESME_ROK', 310 | 'sequence_number': 0, 311 | }, 312 | 'body': { 313 | 'mandatory_parameters': { 314 | 'service_type':'', 315 | 'source_addr_ton':1, 316 | 'source_addr_npi':1, 317 | 'source_addr':'', 318 | 'dest_addr_ton':1, 319 | 'dest_addr_npi':1, 320 | 'destination_addr':'', 321 | 'esm_class':0, 322 | 'protocol_id':0, 323 | 'priority_flag':0, 324 | 'schedule_delivery_time':'', 325 | 'validity_period':'', 326 | 'registered_delivery':0, 327 | 'replace_if_present_flag':0, 328 | 'data_coding':0, 329 | 'sm_default_msg_id':0, 330 | 'sm_length':1, 331 | 'short_message':'', 332 | }, 333 | }, 334 | }, 335 | { 336 | 'header': { 337 | 'command_length': 0, 338 | 'command_id': 'deliver_sm_resp', 339 | 'command_status': 'ESME_ROK', 340 | 'sequence_number': 0, 341 | }, 342 | 'body': { 343 | 'mandatory_parameters': { 344 | 'message_id':'', 345 | }, 346 | }, 347 | }, 348 | { 349 | 'header': { 350 | 'command_length': 0, 351 | 'command_id': 'data_sm', 352 | 'command_status': 'ESME_ROK', 353 | 'sequence_number': 0, 354 | }, 355 | 'body': { 356 | 'mandatory_parameters': { 357 | 'service_type':'', 358 | 'source_addr_ton':1, 359 | 'source_addr_npi':1, 360 | 'source_addr':'', 361 | 'dest_addr_ton':1, 362 | 'dest_addr_npi':1, 363 | 'destination_addr':'', 364 | 'esm_class':0, 365 | 'registered_delivery':0, 366 | 'data_coding':0, 367 | }, 368 | 'optional_parameters': [ 369 | { 370 | 'tag':'message_payload', 371 | 'length':0, 372 | 'value':'', 373 | }, 374 | ], 375 | }, 376 | }, 377 | { 378 | 'header': { 379 | 'command_length': 0, 380 | 'command_id': 'data_sm_resp', 381 | 'command_status': 'ESME_ROK', 382 | 'sequence_number': 0, 383 | }, 384 | 'body': { 385 | 'mandatory_parameters': { 386 | 'message_id':'', 387 | }, 388 | }, 389 | }, 390 | { 391 | 'header': { 392 | 'command_length': 0, 393 | 'command_id': 'query_sm', 394 | 'command_status': 'ESME_ROK', 395 | 'sequence_number': 0, 396 | }, 397 | 'body': { 398 | 'mandatory_parameters': { 399 | 'message_id':'', 400 | 'source_addr_ton':1, 401 | 'source_addr_npi':1, 402 | 'source_addr':'', 403 | }, 404 | }, 405 | }, 406 | { 407 | 'header': { 408 | 'command_length': 0, 409 | 'command_id': 'query_sm_resp', 410 | 'command_status': 'ESME_ROK', 411 | 'sequence_number': 0, 412 | }, 413 | 'body': { 414 | 'mandatory_parameters': { 415 | 'message_id':'', 416 | 'final_date':'', 417 | 'message_state':0, 418 | 'error_code':0, 419 | }, 420 | }, 421 | }, 422 | { 423 | 'header': { 424 | 'command_length': 0, 425 | 'command_id': 'cancel_sm', 426 | 'command_status': 'ESME_ROK', 427 | 'sequence_number': 0, 428 | }, 429 | 'body': { 430 | 'mandatory_parameters': { 431 | 'service_type':'', 432 | 'message_id':'', 433 | 'source_addr_ton':1, 434 | 'source_addr_npi':1, 435 | 'source_addr':'', 436 | 'dest_addr_ton':1, 437 | 'dest_addr_npi':1, 438 | 'destination_addr':'', 439 | }, 440 | }, 441 | }, 442 | { 443 | 'header': { 444 | 'command_length': 0, 445 | 'command_id': 'cancel_sm_resp', 446 | 'command_status': 'ESME_ROK', 447 | 'sequence_number': 0, 448 | }, 449 | }, 450 | { 451 | 'header': { 452 | 'command_length': 0, 453 | 'command_id': 'replace_sm', 454 | 'command_status': 'ESME_ROK', 455 | 'sequence_number': 0, 456 | }, 457 | 'body': { 458 | 'mandatory_parameters': { 459 | 'message_id':'', 460 | 'source_addr_ton':1, 461 | 'source_addr_npi':1, 462 | 'source_addr':'', 463 | 'schedule_delivery_time':'', 464 | 'validity_period':'', 465 | 'registered_delivery':0, 466 | 'replace_if_present_flag':0, 467 | 'data_coding':0, 468 | 'sm_default_msg_id':0, 469 | 'sm_length':1, 470 | 'short_message':'is this an = sign?', 471 | }, 472 | }, 473 | }, 474 | { 475 | 'header': { 476 | 'command_length': 0, 477 | 'command_id': 'replace_sm_resp', 478 | 'command_status': 'ESME_ROK', 479 | 'sequence_number': 0, 480 | }, 481 | }, 482 | { 483 | 'header': { 484 | 'command_length': 0, 485 | 'command_id': 'enquire_link', 486 | 'command_status': 'ESME_ROK', 487 | 'sequence_number': 0, 488 | }, 489 | }, 490 | { 491 | 'header': { 492 | 'command_length': 0, 493 | 'command_id': 'enquire_link_resp', 494 | 'command_status': 'ESME_ROK', 495 | 'sequence_number': 0, 496 | }, 497 | }, 498 | { 499 | 'header': { 500 | 'command_length': 0, 501 | 'command_id': 'alert_notification', 502 | 'command_status': 'ESME_ROK', 503 | 'sequence_number': 0, 504 | }, 505 | 'body': { 506 | 'mandatory_parameters': { 507 | 'source_addr_ton':'international', 508 | 'source_addr_npi':1, 509 | 'source_addr':'', 510 | 'esme_addr_ton':9, 511 | 'esme_addr_npi':'', 512 | 'esme_addr':'', 513 | }, 514 | }, 515 | }, 516 | ] 517 | -------------------------------------------------------------------------------- /test/pdu_asserts.py: -------------------------------------------------------------------------------- 1 | 2 | ######################################## 3 | pdu_json_0000000001 = '''{ 4 | "body": { 5 | "mandatory_parameters": { 6 | "addr_npi": "ISDN", 7 | "addr_ton": "international", 8 | "address_range": "", 9 | "interface_version": "34", 10 | "password": "abc123", 11 | "system_id": "test_system", 12 | "system_type": "" 13 | } 14 | }, 15 | "header": { 16 | "command_id": "bind_transmitter", 17 | "command_length": 40, 18 | "command_status": "ESME_ROK", 19 | "sequence_number": 0 20 | } 21 | }''' 22 | 23 | ######################################## 24 | pdu_json_0000000002 = '''{ 25 | "body": { 26 | "mandatory_parameters": { 27 | "system_id": "test_system" 28 | } 29 | }, 30 | "header": { 31 | "command_id": "bind_transmitter_resp", 32 | "command_length": 28, 33 | "command_status": "ESME_ROK", 34 | "sequence_number": 0 35 | } 36 | }''' 37 | 38 | ######################################## 39 | pdu_json_0000000003 = '''{ 40 | "body": { 41 | "mandatory_parameters": { 42 | "addr_npi": "ISDN", 43 | "addr_ton": "international", 44 | "address_range": "", 45 | "interface_version": "34", 46 | "password": "abc123", 47 | "system_id": "test_system", 48 | "system_type": "" 49 | } 50 | }, 51 | "header": { 52 | "command_id": "bind_receiver", 53 | "command_length": 40, 54 | "command_status": "ESME_ROK", 55 | "sequence_number": 0 56 | } 57 | }''' 58 | 59 | ######################################## 60 | pdu_json_0000000004 = '''{ 61 | "body": { 62 | "mandatory_parameters": { 63 | "system_id": "test_system" 64 | } 65 | }, 66 | "header": { 67 | "command_id": "bind_receiver_resp", 68 | "command_length": 28, 69 | "command_status": "ESME_ROK", 70 | "sequence_number": 0 71 | } 72 | }''' 73 | 74 | ######################################## 75 | pdu_json_0000000005 = '''{ 76 | "body": { 77 | "mandatory_parameters": { 78 | "addr_npi": "ISDN", 79 | "addr_ton": "international", 80 | "address_range": "", 81 | "interface_version": "34", 82 | "password": "abc123", 83 | "system_id": "test_system", 84 | "system_type": "" 85 | } 86 | }, 87 | "header": { 88 | "command_id": "bind_transceiver", 89 | "command_length": 40, 90 | "command_status": "ESME_ROK", 91 | "sequence_number": 0 92 | } 93 | }''' 94 | 95 | ######################################## 96 | pdu_json_0000000006 = '''{ 97 | "body": { 98 | "mandatory_parameters": { 99 | "system_id": "test_system" 100 | } 101 | }, 102 | "header": { 103 | "command_id": "bind_transceiver_resp", 104 | "command_length": 28, 105 | "command_status": "ESME_ROK", 106 | "sequence_number": 0 107 | } 108 | }''' 109 | 110 | ######################################## 111 | pdu_json_0000000007 = '''{ 112 | "body": { 113 | "mandatory_parameters": { 114 | "password": "abc123", 115 | "system_id": "test_system" 116 | } 117 | }, 118 | "header": { 119 | "command_id": "outbind", 120 | "command_length": 35, 121 | "command_status": "ESME_ROK", 122 | "sequence_number": 0 123 | } 124 | }''' 125 | 126 | ######################################## 127 | pdu_json_0000000008 = '''{ 128 | "header": { 129 | "command_id": "unbind", 130 | "command_length": 16, 131 | "command_status": "ESME_ROK", 132 | "sequence_number": 0 133 | } 134 | }''' 135 | 136 | ######################################## 137 | pdu_json_0000000009 = '''{ 138 | "header": { 139 | "command_id": "unbind_resp", 140 | "command_length": 16, 141 | "command_status": "ESME_ROK", 142 | "sequence_number": 0 143 | } 144 | }''' 145 | 146 | ######################################## 147 | pdu_json_0000000010 = '''{ 148 | "header": { 149 | "command_id": "generic_nack", 150 | "command_length": 16, 151 | "command_status": "ESME_ROK", 152 | "sequence_number": 0 153 | } 154 | }''' 155 | 156 | ######################################## 157 | pdu_json_0000000011 = '''{ 158 | "body": { 159 | "mandatory_parameters": { 160 | "data_coding": 0, 161 | "dest_addr_npi": "ISDN", 162 | "dest_addr_ton": "international", 163 | "destination_addr": "", 164 | "esm_class": 0, 165 | "priority_flag": 0, 166 | "protocol_id": 0, 167 | "registered_delivery": 0, 168 | "replace_if_present_flag": 0, 169 | "schedule_delivery_time": "", 170 | "service_type": "", 171 | "short_message": "testing 123", 172 | "sm_default_msg_id": 0, 173 | "sm_length": 11, 174 | "source_addr": "", 175 | "source_addr_npi": "ISDN", 176 | "source_addr_ton": "international", 177 | "validity_period": "" 178 | } 179 | }, 180 | "header": { 181 | "command_id": "submit_sm", 182 | "command_length": 44, 183 | "command_status": "ESME_ROK", 184 | "sequence_number": 0 185 | } 186 | }''' 187 | 188 | ######################################## 189 | pdu_json_0000000012 = '''{ 190 | "body": { 191 | "mandatory_parameters": { 192 | "data_coding": 0, 193 | "dest_addr_npi": "ISDN", 194 | "dest_addr_ton": "international", 195 | "destination_addr": "", 196 | "esm_class": 0, 197 | "priority_flag": 0, 198 | "protocol_id": 0, 199 | "registered_delivery": 0, 200 | "replace_if_present_flag": 0, 201 | "schedule_delivery_time": "", 202 | "service_type": "", 203 | "short_message": null, 204 | "sm_default_msg_id": 0, 205 | "sm_length": 0, 206 | "source_addr": "", 207 | "source_addr_npi": "ISDN", 208 | "source_addr_ton": "international", 209 | "validity_period": "" 210 | }, 211 | "optional_parameters": [ 212 | { 213 | "length": 2, 214 | "tag": "message_payload", 215 | "value": "5666" 216 | } 217 | ] 218 | }, 219 | "header": { 220 | "command_id": "submit_sm", 221 | "command_length": 39, 222 | "command_status": "ESME_ROK", 223 | "sequence_number": 0 224 | } 225 | }''' 226 | 227 | ######################################## 228 | pdu_json_0000000013 = '''{ 229 | "body": { 230 | "mandatory_parameters": { 231 | "message_id": "" 232 | } 233 | }, 234 | "header": { 235 | "command_id": "submit_sm_resp", 236 | "command_length": 17, 237 | "command_status": "ESME_ROK", 238 | "sequence_number": 0 239 | } 240 | }''' 241 | 242 | ######################################## 243 | pdu_json_0000000014 = '''{ 244 | "header": { 245 | "command_id": "submit_sm_resp", 246 | "command_length": 16, 247 | "command_status": "ESME_RSYSERR", 248 | "sequence_number": 0 249 | } 250 | }''' 251 | 252 | ######################################## 253 | pdu_json_0000000015 = '''{ 254 | "body": { 255 | "mandatory_parameters": { 256 | "data_coding": 0, 257 | "dest_address": [ 258 | { 259 | "dest_addr_npi": "ISDN", 260 | "dest_addr_ton": "international", 261 | "dest_flag": 1, 262 | "destination_addr": "the address" 263 | }, 264 | { 265 | "dest_flag": 2, 266 | "dl_name": "the list" 267 | }, 268 | { 269 | "dest_flag": 2, 270 | "dl_name": "the other list" 271 | } 272 | ], 273 | "esm_class": 0, 274 | "number_of_dests": 3, 275 | "priority_flag": 0, 276 | "protocol_id": 0, 277 | "registered_delivery": 0, 278 | "replace_if_present_flag": 0, 279 | "schedule_delivery_time": "", 280 | "service_type": "", 281 | "short_message": "testing 123", 282 | "sm_default_msg_id": 0, 283 | "sm_length": 11, 284 | "source_addr": "", 285 | "source_addr_npi": "ISDN", 286 | "source_addr_ton": "international", 287 | "validity_period": "" 288 | } 289 | }, 290 | "header": { 291 | "command_id": "submit_multi", 292 | "command_length": 83, 293 | "command_status": "ESME_ROK", 294 | "sequence_number": 0 295 | } 296 | }''' 297 | 298 | ######################################## 299 | pdu_json_0000000016 = '''{ 300 | "body": { 301 | "mandatory_parameters": { 302 | "message_id": "", 303 | "no_unsuccess": 2, 304 | "unsuccess_sme": [ 305 | { 306 | "dest_addr_npi": "ISDN", 307 | "dest_addr_ton": "international", 308 | "destination_addr": "", 309 | "error_status_code": 0 310 | }, 311 | { 312 | "dest_addr_npi": "ISDN", 313 | "dest_addr_ton": "network_specific", 314 | "destination_addr": "555", 315 | "error_status_code": 0 316 | } 317 | ] 318 | } 319 | }, 320 | "header": { 321 | "command_id": "submit_multi_resp", 322 | "command_length": 35, 323 | "command_status": "ESME_ROK", 324 | "sequence_number": 0 325 | } 326 | }''' 327 | 328 | ######################################## 329 | pdu_json_0000000017 = '''{ 330 | "body": { 331 | "mandatory_parameters": { 332 | "data_coding": 0, 333 | "dest_addr_npi": "ISDN", 334 | "dest_addr_ton": "international", 335 | "destination_addr": "", 336 | "esm_class": 0, 337 | "priority_flag": 0, 338 | "protocol_id": 0, 339 | "registered_delivery": 0, 340 | "replace_if_present_flag": 0, 341 | "schedule_delivery_time": "", 342 | "service_type": "", 343 | "short_message": null, 344 | "sm_default_msg_id": 0, 345 | "sm_length": 0, 346 | "source_addr": "", 347 | "source_addr_npi": "ISDN", 348 | "source_addr_ton": "international", 349 | "validity_period": "" 350 | } 351 | }, 352 | "header": { 353 | "command_id": "deliver_sm", 354 | "command_length": 33, 355 | "command_status": "ESME_ROK", 356 | "sequence_number": 0 357 | } 358 | }''' 359 | 360 | ######################################## 361 | pdu_json_0000000018 = '''{ 362 | "body": { 363 | "mandatory_parameters": { 364 | "message_id": "" 365 | } 366 | }, 367 | "header": { 368 | "command_id": "deliver_sm_resp", 369 | "command_length": 17, 370 | "command_status": "ESME_ROK", 371 | "sequence_number": 0 372 | } 373 | }''' 374 | 375 | ######################################## 376 | pdu_json_0000000019 = '''{ 377 | "body": { 378 | "mandatory_parameters": { 379 | "data_coding": 0, 380 | "dest_addr_npi": "ISDN", 381 | "dest_addr_ton": "international", 382 | "destination_addr": "", 383 | "esm_class": 0, 384 | "registered_delivery": 0, 385 | "service_type": "", 386 | "source_addr": "", 387 | "source_addr_npi": "ISDN", 388 | "source_addr_ton": "international" 389 | }, 390 | "optional_parameters": [ 391 | { 392 | "length": 0, 393 | "tag": "message_payload", 394 | "value": null 395 | } 396 | ] 397 | }, 398 | "header": { 399 | "command_id": "data_sm", 400 | "command_length": 30, 401 | "command_status": "ESME_ROK", 402 | "sequence_number": 0 403 | } 404 | }''' 405 | 406 | ######################################## 407 | pdu_json_0000000020 = '''{ 408 | "body": { 409 | "mandatory_parameters": { 410 | "message_id": "" 411 | } 412 | }, 413 | "header": { 414 | "command_id": "data_sm_resp", 415 | "command_length": 17, 416 | "command_status": "ESME_ROK", 417 | "sequence_number": 0 418 | } 419 | }''' 420 | 421 | ######################################## 422 | pdu_json_0000000021 = '''{ 423 | "body": { 424 | "mandatory_parameters": { 425 | "message_id": "", 426 | "source_addr": "", 427 | "source_addr_npi": "ISDN", 428 | "source_addr_ton": "international" 429 | } 430 | }, 431 | "header": { 432 | "command_id": "query_sm", 433 | "command_length": 20, 434 | "command_status": "ESME_ROK", 435 | "sequence_number": 0 436 | } 437 | }''' 438 | 439 | ######################################## 440 | pdu_json_0000000022 = '''{ 441 | "body": { 442 | "mandatory_parameters": { 443 | "error_code": 0, 444 | "final_date": "", 445 | "message_id": "", 446 | "message_state": 0 447 | } 448 | }, 449 | "header": { 450 | "command_id": "query_sm_resp", 451 | "command_length": 20, 452 | "command_status": "ESME_ROK", 453 | "sequence_number": 0 454 | } 455 | }''' 456 | 457 | ######################################## 458 | pdu_json_0000000023 = '''{ 459 | "body": { 460 | "mandatory_parameters": { 461 | "dest_addr_npi": "ISDN", 462 | "dest_addr_ton": "international", 463 | "destination_addr": "", 464 | "message_id": "", 465 | "service_type": "", 466 | "source_addr": "", 467 | "source_addr_npi": "ISDN", 468 | "source_addr_ton": "international" 469 | } 470 | }, 471 | "header": { 472 | "command_id": "cancel_sm", 473 | "command_length": 24, 474 | "command_status": "ESME_ROK", 475 | "sequence_number": 0 476 | } 477 | }''' 478 | 479 | ######################################## 480 | pdu_json_0000000024 = '''{ 481 | "header": { 482 | "command_id": "cancel_sm_resp", 483 | "command_length": 16, 484 | "command_status": "ESME_ROK", 485 | "sequence_number": 0 486 | } 487 | }''' 488 | 489 | ######################################## 490 | pdu_json_0000000025 = '''{ 491 | "body": { 492 | "mandatory_parameters": { 493 | "data_coding": 0, 494 | "message_id": "", 495 | "registered_delivery": 0, 496 | "replace_if_present_flag": 0, 497 | "schedule_delivery_time": "", 498 | "short_message": "is this an = sign?", 499 | "sm_default_msg_id": 0, 500 | "sm_length": 18, 501 | "source_addr": "", 502 | "source_addr_npi": "ISDN", 503 | "source_addr_ton": "international", 504 | "validity_period": "" 505 | } 506 | }, 507 | "header": { 508 | "command_id": "replace_sm", 509 | "command_length": 45, 510 | "command_status": "ESME_ROK", 511 | "sequence_number": 0 512 | } 513 | }''' 514 | 515 | ######################################## 516 | pdu_json_0000000026 = '''{ 517 | "header": { 518 | "command_id": "replace_sm_resp", 519 | "command_length": 16, 520 | "command_status": "ESME_ROK", 521 | "sequence_number": 0 522 | } 523 | }''' 524 | 525 | ######################################## 526 | pdu_json_0000000027 = '''{ 527 | "header": { 528 | "command_id": "enquire_link", 529 | "command_length": 16, 530 | "command_status": "ESME_ROK", 531 | "sequence_number": 0 532 | } 533 | }''' 534 | 535 | ######################################## 536 | pdu_json_0000000028 = '''{ 537 | "header": { 538 | "command_id": "enquire_link_resp", 539 | "command_length": 16, 540 | "command_status": "ESME_ROK", 541 | "sequence_number": 0 542 | } 543 | }''' 544 | 545 | ######################################## 546 | pdu_json_0000000029 = '''{ 547 | "body": { 548 | "mandatory_parameters": { 549 | "esme_addr": "", 550 | "esme_addr_npi": "unknown", 551 | "esme_addr_ton": 9, 552 | "source_addr": "", 553 | "source_addr_npi": "ISDN", 554 | "source_addr_ton": "international" 555 | } 556 | }, 557 | "header": { 558 | "command_id": "alert_notification", 559 | "command_length": 22, 560 | "command_status": "ESME_ROK", 561 | "sequence_number": 0 562 | } 563 | }''' 564 | -------------------------------------------------------------------------------- /test/pdu_hex.py: -------------------------------------------------------------------------------- 1 | 2 | pdu_hex_strings = [ 3 | ''' 4 | 0000003C # command_length 5 | 00000004 # command_id 6 | 00000000 # command_status 7 | 00000005 # sequence_number 8 | 00 9 | 02 10 | 08 11 | 35353500 12 | 01 13 | 01 14 | 35353535353535353500 15 | 00 16 | 00 17 | 00 18 | 00 19 | 00 20 | 00 21 | 00 22 | 00 23 | 00 24 | 0F 25 | 48656C6C6F2077696B697065646961 26 | 00000000 27 | 001d00026566 28 | ''', 29 | 30 | ''' 31 | 00000000 # command_length 32 | 00000021 # command_id 33 | 00000000 # command_status 34 | 00000000 # sequence_number 35 | 00 36 | 00 37 | 00 38 | 00 39 | 02 40 | 01 01 01 6500 41 | 02 6600 42 | 00 43 | 00 44 | 00 45 | 00 46 | 00 47 | 00 48 | 00 49 | 00 50 | 00 51 | 00 52 | 0005 0002 0000 53 | 0000 0004 00000000 54 | ''', 55 | 56 | ''' 57 | 00000000 58 | 80000021 59 | 00000000 60 | 00000000 61 | 00 62 | 02 63 | 01016565650000000000 64 | 01016666660000000000 65 | ''', 66 | 67 | ] 68 | 69 | -------------------------------------------------------------------------------- /test/pdu_hex_asserts.py: -------------------------------------------------------------------------------- 1 | 2 | ######################################## 3 | pdu_json_0000000001 = '''{ 4 | "body": { 5 | "mandatory_parameters": { 6 | "data_coding": 0, 7 | "dest_addr_npi": "ISDN", 8 | "dest_addr_ton": "international", 9 | "destination_addr": "555555555", 10 | "esm_class": 0, 11 | "priority_flag": 0, 12 | "protocol_id": 0, 13 | "registered_delivery": 0, 14 | "replace_if_present_flag": 0, 15 | "schedule_delivery_time": "", 16 | "service_type": "", 17 | "short_message": "Hello wikipedia", 18 | "sm_default_msg_id": 0, 19 | "sm_length": 15, 20 | "source_addr": "555", 21 | "source_addr_npi": "national", 22 | "source_addr_ton": "national", 23 | "validity_period": "" 24 | }, 25 | "optional_parameters": [ 26 | { 27 | "length": 0, 28 | "tag": "0000", 29 | "value": null 30 | }, 31 | { 32 | "length": 2, 33 | "tag": "additional_status_info_text", 34 | "value": "ef" 35 | } 36 | ] 37 | }, 38 | "header": { 39 | "command_id": "submit_sm", 40 | "command_length": 60, 41 | "command_status": "ESME_ROK", 42 | "sequence_number": 5 43 | } 44 | }''' 45 | 46 | ######################################## 47 | pdu_json_0000000002 = '''{ 48 | "body": { 49 | "mandatory_parameters": { 50 | "data_coding": 0, 51 | "dest_address": [ 52 | { 53 | "dest_addr_npi": "ISDN", 54 | "dest_addr_ton": "international", 55 | "dest_flag": 1, 56 | "destination_addr": "e" 57 | }, 58 | { 59 | "dest_flag": 2, 60 | "dl_name": "f" 61 | } 62 | ], 63 | "esm_class": 0, 64 | "number_of_dests": 2, 65 | "priority_flag": 0, 66 | "protocol_id": 0, 67 | "registered_delivery": 0, 68 | "replace_if_present_flag": 0, 69 | "schedule_delivery_time": "", 70 | "service_type": "", 71 | "short_message": null, 72 | "sm_default_msg_id": 0, 73 | "sm_length": 0, 74 | "source_addr": "", 75 | "source_addr_npi": "unknown", 76 | "source_addr_ton": "unknown", 77 | "validity_period": "" 78 | }, 79 | "optional_parameters": [ 80 | { 81 | "length": 2, 82 | "tag": "dest_addr_subunit", 83 | "value": 0 84 | }, 85 | { 86 | "length": 4, 87 | "tag": "0000", 88 | "value": "00000000" 89 | } 90 | ] 91 | }, 92 | "header": { 93 | "command_id": "submit_multi", 94 | "command_length": 0, 95 | "command_status": "ESME_ROK", 96 | "sequence_number": 0 97 | } 98 | }''' 99 | 100 | ######################################## 101 | pdu_json_0000000003 = '''{ 102 | "body": { 103 | "mandatory_parameters": { 104 | "message_id": "", 105 | "no_unsuccess": 2, 106 | "unsuccess_sme": [ 107 | { 108 | "dest_addr_npi": "ISDN", 109 | "dest_addr_ton": "international", 110 | "destination_addr": "eee", 111 | "error_status_code": 0 112 | }, 113 | { 114 | "dest_addr_npi": "ISDN", 115 | "dest_addr_ton": "international", 116 | "destination_addr": "fff", 117 | "error_status_code": 0 118 | } 119 | ] 120 | } 121 | }, 122 | "header": { 123 | "command_id": "submit_multi_resp", 124 | "command_length": 0, 125 | "command_status": "ESME_ROK", 126 | "sequence_number": 0 127 | } 128 | }''' 129 | -------------------------------------------------------------------------------- /test/test_multipart.py: -------------------------------------------------------------------------------- 1 | 2 | import unittest, collections 3 | 4 | from smpp.pdu_builder import * 5 | from smpp.pdu_inspector import * 6 | 7 | 8 | class MultipartTestCase(unittest.TestCase): 9 | 10 | def setUp(self): 11 | pass 12 | 13 | def tearDown(self): 14 | pass 15 | 16 | def test_formats(self): 17 | """ 18 | Testing TLV vs SAR vs CSM vs CSM16 19 | """ 20 | tlv = DeliverSM(1, short_message='the first message part') 21 | tlv.set_sar_msg_ref_num(65017) 22 | tlv.set_sar_total_segments(2) 23 | tlv.set_sar_segment_seqnum(1) 24 | sar = DeliverSM(1, short_message='\x00\x03\xff\x02\x01the first message part') 25 | csm = DeliverSM(1, short_message='\x05\x00\x03\xff\x02\x01the first message part') 26 | csm16 = DeliverSM(1, short_message='\x06\x00\x04\xff\xff\x02\x01the first message part') 27 | non_multi = DeliverSM(1, short_message='whatever') 28 | none_short_message = DeliverSM(1, short_message=None) 29 | 30 | self.assertEquals(detect_multipart(unpack_pdu(tlv.get_bin()))['multipart_type'], 'TLV') 31 | self.assertEquals(detect_multipart(unpack_pdu(sar.get_bin()))['multipart_type'], 'SAR') 32 | self.assertEquals(detect_multipart(unpack_pdu(csm.get_bin()))['multipart_type'], 'CSM') 33 | self.assertEquals(detect_multipart(unpack_pdu(csm16.get_bin()))['multipart_type'], 'CSM16') 34 | self.assertEquals(detect_multipart(unpack_pdu(none_short_message.get_bin())), None) 35 | 36 | 37 | def test_ordering(self): 38 | """ 39 | Out of order pieces must be re-assembled in-order 40 | """ 41 | sar_1 = DeliverSM(1, short_message='\x00\x03\xff\x04\x01There she was just a') 42 | sar_2 = DeliverSM(1, short_message='\x00\x03\xff\x04\x02 walking down the street,') 43 | sar_3 = DeliverSM(1, short_message='\x00\x03\xff\x04\x03 singing doo wa diddy') 44 | sar_4 = DeliverSM(1, short_message='\x00\x03\xff\x04\x04 diddy dum diddy do') 45 | 46 | multi = MultipartMessage() 47 | multi.add_pdu(sar_3.get_obj()) 48 | multi.add_pdu(sar_4.get_obj()) 49 | multi.add_pdu(sar_2.get_obj()) 50 | multi.add_pdu(sar_1.get_obj()) 51 | self.assertEquals(multi.get_completed()['message'], 52 | 'There she was just a walking down the street, singing doo wa diddy diddy dum diddy do') 53 | 54 | 55 | def test_real_csm_data(self): 56 | """ 57 | Test with real-world data which uses the CSM format. 58 | """ 59 | 60 | asif_1 = {'body': {'mandatory_parameters': {'priority_flag': 0, 'source_addr': '261xxx720371', 'protocol_id': 0, 'replace_if_present_flag': 0, 'registered_delivery': 0, 'dest_addr_ton': 'international', 'source_addr_npi': 'ISDN', 'schedule_delivery_time': '', 'dest_addr_npi': 'ISDN', 'sm_length': 159, 'esm_class': 64, 'data_coding': 0, 'service_type': '', 'source_addr_ton': 'international', 'sm_default_msg_id': 0, 'validity_period': '', 'destination_addr': '261xxx782943', 'short_message': '\x05\x00\x03\x1a\x02\x01I try to send sms testing vumi sms sms sms sms msm sms sms sms sms sms sms sms sms sms ssms sms smS sms sms sms sms sms sms sms sns sns sms sms sms sms s'}, 'optional_parameters': [{'length': 2, 'tag': 'user_message_reference', 'value': 91}, {'length': 16, 'tag': 'dest_subaddress', 'value': 'a0000410020601030303070802090403'}]}, 'header': {'command_status': 'ESME_ROK', 'command_length': 242, 'sequence_number': 23, 'command_id': 'deliver_sm'}} 61 | 62 | asif_2 = {'body': {'mandatory_parameters': {'priority_flag': 1, 'source_addr': '261xxx720371', 'protocol_id': 0, 'replace_if_present_flag': 0, 'registered_delivery': 0, 'dest_addr_ton': 'international', 'source_addr_npi': 'ISDN', 'schedule_delivery_time': '', 'dest_addr_npi': 'ISDN', 'sm_length': 78, 'esm_class': 64, 'data_coding': 0, 'service_type': '', 'source_addr_ton': 'international', 'sm_default_msg_id': 0, 'validity_period': '', 'destination_addr': '261xxx782943', 'short_message': '\x05\x00\x03\x1a\x02\x02mns again again again again again again again again sms sms sms sms sms '}, 'optional_parameters': [{'length': 2, 'tag': 'user_message_reference', 'value': 92}, {'length': 16, 'tag': 'dest_subaddress', 'value': 'a0000410020601030303070802090403'}]}, 'header': {'command_status': 'ESME_ROK', 'command_length': 161, 'sequence_number': 24, 'command_id': 'deliver_sm'}} 63 | 64 | multi = MultipartMessage() 65 | self.assertEquals(multi.get_partial(), {'to_msisdn': '', 'from_msisdn': '', 'message': ''}) 66 | self.assertEquals(multi.get_completed(), None) 67 | self.assertEquals(multi.get_key(), None) 68 | multi.add_pdu(asif_2) 69 | self.assertEquals(multi.get_partial(), {'to_msisdn': '261xxx782943', 'from_msisdn': '261xxx720371', 'message': 'mns again again again again again again again again sms sms sms sms sms '}) 70 | self.assertEquals(multi.get_completed(), None) 71 | self.assertEquals(multi.get_key(), '261xxx720371_261xxx782943_26_2') 72 | multi.add_pdu(asif_1) 73 | self.assertEquals(multi.get_completed()['message'], 'I try to send sms testing vumi sms sms sms sms msm sms sms sms sms sms sms sms sms sms ssms sms smS sms sms sms sms sms sms sms sns sns sms sms sms sms smns again again again again again again again again sms sms sms sms sms ') 74 | self.assertEquals(multi.get_key(), '261xxx720371_261xxx782943_26_2') 75 | 76 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import unittest, collections 3 | from datetime import datetime, timedelta 4 | 5 | from smpp.esme import * 6 | from smpp.clickatell import * 7 | from smpp import pdu 8 | import credentials_test 9 | try:import credentials_priv 10 | except:pass 11 | 12 | from test.pdu import pdu_objects 13 | from test import pdu_asserts 14 | from test.pdu_hex import pdu_hex_strings 15 | from test import pdu_hex_asserts 16 | 17 | 18 | def unpack_hex(pdu_hex): 19 | """Unpack PDU hex string and return it as a dictionary""" 20 | return unpack_pdu(binascii.a2b_hex(hexclean(pdu_hex))) 21 | 22 | def hexclean(dirtyhex): 23 | """Remove whitespace, comments & newlines from hex string""" 24 | return re.sub(r'\s','',re.sub(r'#.*\n','\n',dirtyhex)) 25 | 26 | def prettydump(pdu_obj): 27 | """Unpack PDU dictionary and dump it as a JSON formatted string""" 28 | return json.dumps(pdu_obj, indent=4, sort_keys=True) 29 | 30 | def hex_to_named(dictionary): 31 | """ 32 | Recursive function to convert values in test dictionaries to 33 | their named counterparts that unpack_pdu returns 34 | """ 35 | clone = dictionary.copy() 36 | for key, value in clone.items(): 37 | if isinstance(value, collections.Mapping): 38 | clone[key] = hex_to_named(value) 39 | else: 40 | lookup_table = pdu.maps.get('%s_by_hex' % key) 41 | if lookup_table: 42 | # overwrite with mapped value or keep using 43 | # default if the dictionary key doesn't exist 44 | clone[key] = lookup_table.get("%.2d" % value, value) 45 | return clone 46 | 47 | def create_pdu_asserts(): 48 | pdu_index = 0 49 | for pdu in pdu_objects: 50 | pdu_index += 1 51 | pstr = "\n########################################\n" 52 | pstr += "pdu_json_" 53 | pstr += ('%010d' % pdu_index) 54 | pstr += " = '''" 55 | pstr += prettydump(unpack_pdu(pack_pdu(pdu))) 56 | pstr += "'''" 57 | print pstr 58 | 59 | 60 | def create_pdu_hex_asserts(): 61 | pdu_index = 0 62 | for pdu_hex in pdu_hex_strings: 63 | pdu_index += 1 64 | pstr = "\n########################################\n" 65 | pstr += "pdu_json_" 66 | pstr += ('%010d' % pdu_index) 67 | pstr += " = '''" 68 | pstr += prettydump(unpack_hex(pdu_hex)) 69 | pstr += "'''" 70 | print pstr 71 | 72 | 73 | ## :w|!python % > test/pdu_asserts.py 74 | #create_pdu_asserts() 75 | #quit() 76 | 77 | ## :w|!python % > test/pdu_hex_asserts.py 78 | #create_pdu_hex_asserts() 79 | #quit() 80 | 81 | 82 | class PduTestCase(unittest.TestCase): 83 | 84 | def setUp(self): 85 | pass 86 | 87 | def tearDown(self): 88 | pass 89 | 90 | def assertDictEquals(self, dictionary1, dictionary2, depth=[]): 91 | """ 92 | Recursive dictionary comparison, will fail if any keys and values 93 | in the two dictionaries don't match. Displays the key chain / depth 94 | and which parts of the two dictionaries didn't match. 95 | """ 96 | d1_keys = dictionary1.keys() 97 | d1_keys.sort() 98 | 99 | d2_keys = dictionary2.keys() 100 | d2_keys.sort() 101 | 102 | self.failUnlessEqual(d1_keys, d2_keys, 103 | "Dictionary keys do not match, %s vs %s" % ( 104 | d1_keys, d2_keys)) 105 | for key, value in dictionary1.items(): 106 | if isinstance(value, collections.Mapping): 107 | # go recursive 108 | depth.append(key) 109 | self.assertDictEquals(value, dictionary2[key], depth) 110 | else: 111 | self.failUnlessEqual(value, dictionary2[key], 112 | "Dictionary values do not match for key '%s' " \ 113 | "(%s vs %s) at depth: %s.\nDictionary 1: %s\n" \ 114 | "Dictionary 2: %s\n" % ( 115 | key, value, dictionary2[key], ".".join(depth), 116 | prettydump(dictionary1), prettydump(dictionary2))) 117 | 118 | def test_pack_unpack_pdu_objects(self): 119 | print '' 120 | """ 121 | Take a dictionary, pack and unpack it and dump it as JSON correctly 122 | """ 123 | pdu_index = 0 124 | for pdu in pdu_objects: 125 | pdu_index += 1 126 | padded_index = '%010d' % pdu_index 127 | print '...', padded_index 128 | self.assertEquals( 129 | re.sub('\n *','', 130 | prettydump(unpack_pdu(pack_pdu(pdu)))), 131 | re.sub('\n *','', 132 | eval('pdu_asserts.pdu_json_'+padded_index))) 133 | 134 | 135 | def test_pack_unpack_pdu_hex_strings(self): 136 | print '' 137 | """ 138 | Read the hex data, clean it, and unpack it to JSON correctly 139 | """ 140 | pdu_index = 0 141 | for pdu_hex in pdu_hex_strings: 142 | pdu_index += 1 143 | padded_index = '%010d' % pdu_index 144 | print '...', padded_index 145 | self.assertEquals( 146 | re.sub('\n *','', 147 | prettydump(unpack_hex(pdu_hex))), 148 | re.sub('\n *','', 149 | eval('pdu_hex_asserts.pdu_json_'+padded_index))) 150 | 151 | 152 | def test_pack_unpack_performance(self): 153 | print '' 154 | """ 155 | Pack & unpack 500 submit_sm PDUs in under 1 second 156 | """ 157 | submit_sm = { 158 | 'header': { 159 | 'command_length': 0, 160 | 'command_id': 'submit_sm', 161 | 'command_status': 'ESME_ROK', 162 | 'sequence_number': 0, 163 | }, 164 | 'body': { 165 | 'mandatory_parameters': { 166 | 'service_type':'', 167 | 'source_addr_ton':1, 168 | 'source_addr_npi':1, 169 | 'source_addr':'', 170 | 'dest_addr_ton':1, 171 | 'dest_addr_npi':1, 172 | 'destination_addr':'', 173 | 'esm_class':0, 174 | 'protocol_id':0, 175 | 'priority_flag':0, 176 | 'schedule_delivery_time':'', 177 | 'validity_period':'', 178 | 'registered_delivery':0, 179 | 'replace_if_present_flag':0, 180 | 'data_coding':0, 181 | 'sm_default_msg_id':0, 182 | 'sm_length':1, 183 | 'short_message':'', 184 | }, 185 | }, 186 | } 187 | start = datetime.now() 188 | for x in range(500): 189 | x += 1 190 | submit_sm['header']['sequence_number'] = x 191 | sm = 'testing: x = '+str(x)+'' 192 | submit_sm['body']['mandatory_parameters']['short_message'] = sm 193 | u = unpack_pdu(pack_pdu(submit_sm)) 194 | delta = datetime.now() - start 195 | print '... 500 pack & unpacks in:', delta 196 | self.assertTrue(delta < timedelta(seconds=1)) 197 | 198 | def test_pack_unpack_of_unicode(self): 199 | """ 200 | SMPP module should be able to pack & unpack unicode characters 201 | without a problem 202 | """ 203 | submit_sm = { 204 | 'header': { 205 | 'command_length': 67, 206 | 'command_id': 'submit_sm', 207 | 'command_status': 'ESME_ROK', 208 | 'sequence_number': 0, 209 | }, 210 | 'body': { 211 | 'mandatory_parameters': { 212 | 'service_type':'', 213 | 'source_addr_ton':'international', 214 | 'source_addr_npi':'unknown', 215 | 'source_addr':'', 216 | 'dest_addr_ton':'international', 217 | 'dest_addr_npi':'unknown', 218 | 'destination_addr':'', 219 | 'esm_class':0, 220 | 'protocol_id':0, 221 | 'priority_flag':0, 222 | 'schedule_delivery_time':'', 223 | 'validity_period':'', 224 | 'registered_delivery':0, 225 | 'replace_if_present_flag':0, 226 | 'data_coding':0, 227 | 'sm_default_msg_id':0, 228 | 'sm_length':34, 229 | 'short_message':u'Vumi says: أبن الشرموطة'.encode('utf-8'), 230 | }, 231 | }, 232 | } 233 | self.assertDictEquals( 234 | hex_to_named(submit_sm), 235 | unpack_pdu(pack_pdu(submit_sm)) 236 | ) 237 | 238 | def test_pack_unpack_of_ascii_and_unicode_8_16_32(self): 239 | """ 240 | SMPP module should be able to pack & unpack unicode characters 241 | without a problem 242 | """ 243 | submit_sm = { 244 | 'header': { 245 | 'command_length': 65, 246 | 'command_id': 'submit_sm', 247 | 'command_status': 'ESME_ROK', 248 | 'sequence_number': 0, 249 | }, 250 | 'body': { 251 | 'mandatory_parameters': { 252 | 'service_type':'', 253 | 'source_addr_ton':'international', 254 | 'source_addr_npi':'unknown', 255 | 'source_addr':'', 256 | 'dest_addr_ton':'international', 257 | 'dest_addr_npi':'unknown', 258 | 'destination_addr':'', 259 | 'esm_class':0, 260 | 'protocol_id':0, 261 | 'priority_flag':0, 262 | 'schedule_delivery_time':'', 263 | 'validity_period':'', 264 | 'registered_delivery':0, 265 | 'replace_if_present_flag':0, 266 | 'data_coding':0, 267 | 'sm_default_msg_id':0, 268 | 'sm_length':32, 269 | 'short_message':u'a \xf0\x20\u0373\u0020\u0433\u0020\u0533\u0020\u05f3\u0020\u0633\u0020\u13a3\u0020\u16a3 \U0001f090'.encode('utf-8'), 270 | }, 271 | }, 272 | } 273 | self.assertDictEquals( 274 | hex_to_named(submit_sm), 275 | unpack_pdu(pack_pdu(submit_sm)) 276 | ) 277 | 278 | class PduBuilderTestCase(unittest.TestCase): 279 | 280 | def test_true(self): 281 | print '' 282 | self.assertTrue(True) 283 | 284 | 285 | 286 | if __name__ == '__main__': 287 | print '\n##########################################################\n' 288 | #deliv_sm_resp = DeliverSMResp(23) 289 | #print deliv_sm_resp.get_obj() 290 | #print deliv_sm_resp.get_hex() 291 | #enq_lnk = EnquireLink(7) 292 | #print enq_lnk.get_obj() 293 | #print enq_lnk.get_hex() 294 | #sub_sm = SubmitSM(5, short_message='testing testing') 295 | #print sub_sm.get_obj() 296 | #print sub_sm.get_hex() 297 | #sub_sm.add_message_payload('01020304') 298 | #print sub_sm.get_obj() 299 | #print sub_sm.get_hex() 300 | #print unpack_pdu(sub_sm.get_bin()) 301 | print '\n##########################################################\n' 302 | 303 | esme = ESME() 304 | esme.loadDefaults(clickatell_defaults) 305 | esme.loadDefaults(credentials_test.logica) 306 | print esme.defaults 307 | esme.bind_transmitter() 308 | print esme.state 309 | start = datetime.now() 310 | for x in range(1): 311 | esme.submit_sm( 312 | short_message = 'gobbledygook', 313 | destination_addr = '555', 314 | ) 315 | print esme.state 316 | for x in range(1): 317 | esme.submit_multi( 318 | short_message = 'gobbledygook', 319 | dest_address = ['444','333'], 320 | ) 321 | print esme.state 322 | for x in range(1): 323 | esme.submit_multi( 324 | short_message = 'gobbledygook', 325 | dest_address = [ 326 | {'dest_flag':1, 'destination_addr':'111'}, 327 | {'dest_flag':2, 'dl_name':'list22222'}, 328 | ], 329 | ) 330 | print esme.state 331 | delta = datetime.now() - start 332 | esme.disconnect() 333 | print esme.state 334 | print 'excluding binding ... time to send messages =', delta 335 | 336 | 337 | #if __name__ == '__main__': 338 | #unittest.main() 339 | 340 | --------------------------------------------------------------------------------