ruby-smpp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.1.0 2008-04-07
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Apparat AS
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,35 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ examples/sample_gateway.rb
7
+ config/hoe.rb
8
+ config/requirements.rb
9
+ lib/smpp.rb
10
+ lib/smpp/version.rb
11
+ lib/smpp/base.rb
12
+ lib/smpp/pdu/base.rb
13
+ lib/smpp/pdu/bind_transceiver.rb
14
+ lib/smpp/pdu/bind_transceiver_response.rb
15
+ lib/smpp/pdu/deliver_sm.rb
16
+ lib/smpp/pdu/deliver_sm_response.rb
17
+ lib/smpp/pdu/enquire_link.rb
18
+ lib/smpp/pdu/enquire_link_response.rb
19
+ lib/smpp/pdu/generic_nack.rb
20
+ lib/smpp/pdu/submit_sm.rb
21
+ lib/smpp/pdu/submit_sm_response.rb
22
+ lib/smpp/pdu/unbind.rb
23
+ lib/smpp/pdu/unbind_response.rb
24
+ lib/smpp/transceiver.rb
25
+ lib/sms.rb
26
+ log/
27
+ script/console
28
+ script/destroy
29
+ script/generate
30
+ script/txt2html
31
+ setup.rb
32
+ tasks/deployment.rake
33
+ tasks/environment.rake
34
+ test/smpp_test.rb
35
+ test/test_helper.rb
data/README.txt ADDED
@@ -0,0 +1,86 @@
1
+ = Ruby-SMPP
2
+
3
+ * http://ruby-smpp.rubyforge.org
4
+
5
+ == DESCRIPTION:
6
+
7
+ Ruby-SMPP is a Ruby implementation of the SMPP v3.4 protocol. It is suitable for writing gateway daemons that communicate with SMSCs for sending and receiving SMS messages.
8
+
9
+ The implementation is based on the Ruby/EventMachine library.
10
+
11
+ Glossary
12
+ -----------------
13
+ SMSC: SMS Center. Mobile operators normally operate an SMSC in their network. The SMSC stores and forwards SMS messages.
14
+ MO: Mobile Originated SMS: originated in a mobile phone, ie. sent by an end user.
15
+ MT: Mobile Terminated SMS: terminated in a mobile phone, ie. received by an end user.
16
+ DR: Delivery Report, or delivery notification. When you send an MT message, you should receive a DR after a while.
17
+ PDU: Protcol Base Unit, the data units that SMPP is comprised of. This implementation does _not_ implement all SMPP PDUs.
18
+
19
+ Protocol
20
+ -----------------
21
+ The SMPP 3.4 protocol spec can be downloaded here: http://smsforum.net/SMPP_v3_4_Issue1_2.zip
22
+
23
+ Testing
24
+ -----------------
25
+ Logica provides an SMPP simulator that you can download from http://opensmpp.logica.com/. You can
26
+ also sign up for a demo SMPP account at one of the many bulk-SMS providers out there.
27
+
28
+ == FEATURES/PROBLEMS:
29
+
30
+ * Implements only typical client subset of SMPP 3.4 with single-connection Transceiver as opposed to dual-connection Transmitter + Receiver.
31
+ * Contributors are encouraged to add missing PDUs.
32
+ * Need more test cases!
33
+
34
+ == BASIC USAGE:
35
+
36
+ Start the transceiver. Receive callbacks whenever incoming messages or delivery reports arrive. Send messages with Transceiver#send_mt.
37
+
38
+ <pre>
39
+ # connect to SMSC
40
+ tx = EventMachine::run do
41
+ $tx = EventMachine::connect(
42
+ host,
43
+ port,
44
+ Smpp::Transceiver,
45
+ config, # a property hash
46
+ mo_proc, # the proc invoked on incoming (MO) messages
47
+ dr_proc, # the proc invoked on delivery reports
48
+ pdr_storage) # hash-like storage for pending delivery reports
49
+ end
50
+
51
+ # send a message
52
+ tx.send_mt(id, from, to, body)
53
+ </pre>
54
+
55
+ For a more complete example, see examples/sample_gateway.rb
56
+
57
+ == REQUIREMENTS:
58
+
59
+ * Eventmachine 0.10.0
60
+
61
+ == INSTALL:
62
+
63
+ * sudo gem install ruby-smpp
64
+
65
+ == LICENSE:
66
+
67
+ Copyright (c) 2008 Apparat AS
68
+
69
+ Permission is hereby granted, free of charge, to any person obtaining
70
+ a copy of this software and associated documentation files (the
71
+ 'Software'), to deal in the Software without restriction, including
72
+ without limitation the rights to use, copy, modify, merge, publish,
73
+ distribute, sublicense, and/or sell copies of the Software, and to
74
+ permit persons to whom the Software is furnished to do so, subject to
75
+ the following conditions:
76
+
77
+ The above copyright notice and this permission notice shall be
78
+ included in all copies or substantial portions of the Software.
79
+
80
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
81
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
82
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
83
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
84
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
85
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
86
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ task :test do
2
+ ruby "test/smpp_test.rb"
3
+ end
4
+
5
+ require 'config/requirements'
6
+ require 'config/hoe' # setup Hoe + all gem configuration
7
+
8
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
9
+
data/config/hoe.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'smpp/version'
2
+
3
+ AUTHOR = 'August Z. Flatby'
4
+ EMAIL = "august@apparat.no"
5
+ DESCRIPTION="Ruby-implementation of the SMPP protocol, based on EventMachine. SMPP is a protocol that allows ordinary people outside the mobile network to exchange SMS messages directly with mobile operators."
6
+ GEM_NAME = 'ruby-smpp'
7
+ RUBYFORGE_PROJECT = 'ruby-smpp' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "augustzf"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Smpp::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'ruby-smpp documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ $hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.developer(AUTHOR, EMAIL)
52
+ p.description = DESCRIPTION
53
+ p.summary = DESCRIPTION
54
+ p.url = HOMEPATH
55
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
56
+ p.test_globs = ["test/**/test_*.rb"]
57
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
58
+
59
+ # == Optional
60
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
61
+ p.extra_deps = [['eventmachine', '>= 0.10.0']]
62
+
63
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
64
+
65
+ end
66
+
67
+ CHANGES = $hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
68
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
69
+ $hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
70
+ $hoe.rsync_args = '-av --delete --ignore-errors'
@@ -0,0 +1,15 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Sample SMPP SMS Gateway. A proper SMS gateway listens for MOs (incoming
4
+ # messages) and DRs (delivery reports), and submit MTs (outgoing messages).
5
+ #
6
+ # An SMS gateway can be the endpoint for a shortcode. For example, the
7
+ # shortcode 2210 in Norway is implemented as a number of gateways like this
8
+ # (one for each mobile operator). Hence, it will receive MOs (deliver_sm) from
9
+ # end users addressed to shortcode 2210 and send MTs (submit_sm) to end users
10
+ # from shortcode 2210.
11
+ #
12
+ # This gateway logs activity (including incoming MO and DR) to log/sms_gateway.log
13
+ # and accepts outgoing (MT) messages from the console. This may be useful for
14
+ # testing your SMPP setup.
15
+
16
+ gem 'ruby-smpp'
17
+ require 'smpp'
18
+
19
+ # set up logger
20
+ Smpp::Base.logger = Logger.new(File.join(File.dirname(__FILE__), '..', 'log/sms_gateway.log'))
21
+
22
+ # the transceiver
23
+ $tx = nil
24
+
25
+ # We use EventMachine to receive keyboard input (which we send as MT messages).
26
+ # A "real" gateway would probably get its MTs from a message queue instead.
27
+ module KeyboardHandler
28
+ include EventMachine::Protocols::LineText2
29
+
30
+ def receive_line(data)
31
+ puts "Sending MT: #{data}"
32
+ from = '2210'
33
+ to = '4790000000'
34
+ $tx.send_mt(123, from, to, data)
35
+ prompt
36
+ end
37
+ end
38
+
39
+ def prompt
40
+ print "Enter MT body: "
41
+ $stdout.flush
42
+ end
43
+
44
+ def logger
45
+ Smpp::Base.logger
46
+ end
47
+
48
+ def start(config)
49
+ # The transceiver sends MT messages to the SMSC. It needs a storage with Hash-like
50
+ # semantics to map SMSC message IDs to your own message IDs.
51
+ pdr_storage = {}
52
+
53
+ # The block invoked when we receive an MO message from the SMSC
54
+ mo_proc = Proc.new do |sender, receiver, msg|
55
+ begin
56
+ # This is where you'd enqueue or store the MO message for further processing.
57
+ logger.info "Received MO from <#{sender}> to <#{receiver}>: <#{msg}>"
58
+ rescue Exception => ex
59
+ logger.error "Exception processing MO: #{ex}"
60
+ end
61
+ end
62
+
63
+ # Invoked on delivery reports
64
+ dr_proc = Proc.new do |msg_reference, operator_status_code|
65
+ begin
66
+ # The SMSC returns its own message reference. Look up (and delete) our stored objects
67
+ # based on this reference.
68
+ pending_message = pdr_storage.delete(msg_reference)
69
+ logger.info "Received DR for #{pending_message}: #{operator_status_code}"
70
+ rescue Exception => ex
71
+ logger.error "Error processing DR: #{ex}"
72
+ end
73
+ end
74
+
75
+ # Run EventMachine in loop so we can reconnect when the SMSC drops our connection.
76
+ loop do
77
+ EventMachine::run do
78
+ $tx = EventMachine::connect(
79
+ config[:host],
80
+ config[:port],
81
+ Smpp::Transceiver,
82
+ config,
83
+ mo_proc,
84
+ dr_proc,
85
+ pdr_storage)
86
+ # Start consuming MT messages (in this case, from the console)
87
+ # Normally, you'd hook this up to a message queue such as Starling
88
+ # or ActiveMQ via STOMP.
89
+ EventMachine::open_keyboard(KeyboardHandler)
90
+ end
91
+ logger.warn "Event loop stopped. Restarting in 5 seconds.."
92
+ sleep 5
93
+ end
94
+ end
95
+
96
+ # Start the Gateway
97
+ begin
98
+ puts "Starting SMS Gateway"
99
+
100
+ # SMPP properties. These parameters work well with the Logica SMPP simulator.
101
+ # Consult the SMPP spec or your mobile operator for the correct settings of
102
+ # the other properties.
103
+ config = {
104
+ :host => 'localhost',
105
+ :port => 6000,
106
+ :system_id => 'jorge',
107
+ :password => 'jorge',
108
+ :source_ton => 0,
109
+ :source_npi => 1,
110
+ :destination_ton => 1,
111
+ :destination_npi => 1,
112
+ :source_address_range => '',
113
+ :destination_address_range => '',
114
+ :enquire_link_delay_secs => 10
115
+ }
116
+ prompt
117
+ start(config)
118
+ rescue Exception => ex
119
+ puts "Exception in SMS Gateway: #{ex} at #{ex.backtrace[0]}"
120
+ end
data/lib/smpp.rb ADDED
@@ -0,0 +1,21 @@
1
+ # SMPP v3.4 subset implementation.
2
+ # SMPP is a short message peer-to-peer protocol typically used to communicate
3
+ # with SMS Centers (SMSCs) over TCP/IP.
4
+ #
5
+ # August Z. Flatby
6
+ # august@apparat.no
7
+
8
+ require 'logger'
9
+
10
+ $:.unshift(File.dirname(__FILE__))
11
+ require 'smpp/base.rb'
12
+ require 'smpp/transceiver.rb'
13
+ require 'smpp/pdu/base.rb'
14
+
15
+ # Load all PDUs
16
+ Dir.glob(File.join(File.dirname(__FILE__), 'smpp', 'pdu', '*.rb')) do |f|
17
+ require f unless f.match('base.rb$')
18
+ end
19
+
20
+ # Default logger. Invoke this call in your client to use another logger.
21
+ Smpp::Base.logger = Logger.new(STDOUT)
data/lib/smpp/base.rb ADDED
@@ -0,0 +1,124 @@
1
+ require 'timeout'
2
+ require 'iconv'
3
+ require 'scanf'
4
+ require 'monitor'
5
+ require 'eventmachine'
6
+
7
+ module Smpp
8
+ class InvalidStateException < Exception; end
9
+
10
+ class Base < EventMachine::Connection
11
+ include Smpp
12
+
13
+ # :bound or :unbound
14
+ attr_accessor :state
15
+
16
+ def Base.logger
17
+ @@logger
18
+ end
19
+
20
+ def Base.logger=(logger)
21
+ @@logger = logger
22
+ end
23
+
24
+ def logger
25
+ @@logger
26
+ end
27
+
28
+ def initialize(config)
29
+ @config = config
30
+ end
31
+
32
+ # invoked by EventMachine when connected
33
+ def post_init
34
+ # send Bind PDU
35
+ send_bind
36
+
37
+ # start timer that will periodically send enquire link PDUs
38
+ start_enquire_link_timer(@config[:enquire_link_delay_secs]) if @config[:enquire_link_delay_secs]
39
+ rescue Exception => ex
40
+ logger.error "Error starting RX: #{ex.message} at #{ex.backtrace[0]}"
41
+ end
42
+
43
+ def start_enquire_link_timer(delay_secs)
44
+ logger.info "Starting enquire link timer (with #{delay_secs}s interval)"
45
+ EventMachine::PeriodicTimer.new(delay_secs) do
46
+ if error?
47
+ logger.warn "Link timer: Connection is in error state. Terminating loop."
48
+ EventMachine::stop_event_loop
49
+ else
50
+ write_pdu Pdu::EnquireLink.new
51
+ end
52
+ end
53
+ end
54
+
55
+ # EventMachine::Connection#receive_data
56
+ def receive_data(data)
57
+ # parse incoming PDU
58
+ pdu = read_pdu(data)
59
+
60
+ # let subclass process it
61
+ process_pdu(pdu) if pdu
62
+ end
63
+
64
+ # EventMachine::Connection#unbind
65
+ def unbind
66
+ logger.warn "EventMachine: unbind invoked in bound state" if @state == :bound
67
+ end
68
+
69
+ def send_unbind
70
+ #raise rescue logger.debug "Unbinding, now?? #{$!.backtrace[1..5].join("\n")}"
71
+ write_pdu Pdu::Unbind.new
72
+ # leave it to the subclass to process the UnbindResponse
73
+ @state = :unbound
74
+ end
75
+
76
+ # process common PDUs
77
+ # returns true if no further processing necessary
78
+ def process_pdu(pdu)
79
+ case pdu
80
+ when Pdu::EnquireLinkResponse
81
+ # nop
82
+ when Pdu::EnquireLink
83
+ write_pdu(Pdu::EnquireLinkResponse.new(pdu.sequence_number))
84
+ when Pdu::Unbind
85
+ @state = :unbound
86
+ write_pdu(Pdu::UnbindResponse.new(pdu.sequence_number, Pdu::Base::ESME_ROK))
87
+ EventMachine::stop_event_loop
88
+ when Pdu::UnbindResponse
89
+ logger.info "Unbound OK. Closing connection."
90
+ close_connection
91
+ when Pdu::GenericNack
92
+ logger.warn "Received NACK! (error code #{pdu.error_code})."
93
+ # we don't take this lightly: stop the event loop
94
+ EventMachine::stop_event_loop
95
+ else
96
+ logger.warn "(#{self.class.name}) Received unexpected PDU: #{pdu.to_human}."
97
+ EventMachine::stop_event_loop
98
+ end
99
+ end
100
+
101
+ private
102
+ def write_pdu(pdu)
103
+ logger.debug "<- #{pdu.to_human}"
104
+ send_data pdu.data
105
+ end
106
+
107
+ def read_pdu(data)
108
+ pdu = nil
109
+ # we may either receive a new request or a response to a previous response.
110
+ begin
111
+ pdu = Pdu::Base.create(data)
112
+ if !pdu
113
+ logger.warn "Not able to parse PDU!"
114
+ else
115
+ logger.debug "-> " + pdu.to_human
116
+ end
117
+ rescue Exception => ex
118
+ logger.error "Exception while reading PDUs: #{ex} in #{ex.backtrace[0]}"
119
+ raise
120
+ end
121
+ pdu
122
+ end
123
+ end
124
+ end