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 +4 -0
- data/License.txt +20 -0
- data/Manifest.txt +35 -0
- data/README.txt +86 -0
- data/Rakefile +9 -0
- data/config/hoe.rb +70 -0
- data/config/requirements.rb +15 -0
- data/examples/sample_gateway.rb +120 -0
- data/lib/smpp.rb +21 -0
- data/lib/smpp/base.rb +124 -0
- data/lib/smpp/pdu/base.rb +140 -0
- data/lib/smpp/pdu/bind_transceiver.rb +6 -0
- data/lib/smpp/pdu/bind_transceiver_response.rb +7 -0
- data/lib/smpp/pdu/deliver_sm.rb +40 -0
- data/lib/smpp/pdu/deliver_sm_response.rb +5 -0
- data/lib/smpp/pdu/enquire_link.rb +5 -0
- data/lib/smpp/pdu/enquire_link_response.rb +5 -0
- data/lib/smpp/pdu/generic_nack.rb +8 -0
- data/lib/smpp/pdu/submit_sm.rb +44 -0
- data/lib/smpp/pdu/submit_sm_response.rb +8 -0
- data/lib/smpp/pdu/unbind.rb +5 -0
- data/lib/smpp/pdu/unbind_response.rb +5 -0
- data/lib/smpp/transceiver.rb +97 -0
- data/lib/smpp/version.rb +9 -0
- data/lib/sms.rb +9 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/script/txt2html +74 -0
- data/setup.rb +1585 -0
- data/tasks/deployment.rake +34 -0
- data/tasks/environment.rake +7 -0
- data/test/smpp_test.rb +54 -0
- data/test/test_helper.rb +2 -0
- metadata +100 -0
data/History.txt
ADDED
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
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
|