ruby-smpp 0.3.0 → 0.4.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/Gemfile +5 -0
- data/Gemfile.lock +22 -0
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/config/environment.rb +2 -0
- data/lib/smpp.rb +1 -0
- data/lib/smpp/pdu/bind_receiver.rb +4 -0
- data/lib/smpp/pdu/bind_receiver_response.rb +4 -0
- data/lib/smpp/pdu/deliver_sm.rb +17 -2
- data/lib/smpp/receiver.rb +80 -0
- data/ruby-smpp.gemspec +19 -3
- data/test/delegate.rb +28 -0
- data/test/pdu_parsing_test.rb +84 -0
- data/test/receiver_test.rb +197 -0
- data/test/responsive_delegate.rb +53 -0
- data/test/server.rb +56 -0
- data/test/smpp_test.rb +3 -141
- metadata +23 -7
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
eventmachine (0.12.10)
|
5
|
+
gemcutter (0.6.1)
|
6
|
+
git (1.2.5)
|
7
|
+
jeweler (1.4.0)
|
8
|
+
gemcutter (>= 0.1.0)
|
9
|
+
git (>= 1.2.5)
|
10
|
+
rubyforge (>= 2.0.0)
|
11
|
+
json_pure (1.4.6)
|
12
|
+
rake (0.8.7)
|
13
|
+
rubyforge (2.0.4)
|
14
|
+
json_pure (>= 1.1.7)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
eventmachine
|
21
|
+
jeweler
|
22
|
+
rake
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/lib/smpp.rb
CHANGED
data/lib/smpp/pdu/deliver_sm.rb
CHANGED
@@ -27,7 +27,7 @@ class Smpp::Pdu::DeliverSm < Smpp::Pdu::Base
|
|
27
27
|
@data_coding = options[:data_coding]?options[:data_coding]:3 # iso-8859-1
|
28
28
|
@sm_default_msg_id = options[:sm_default_msg_id]?options[:sm_default_msg_id]:0
|
29
29
|
@short_message = short_message
|
30
|
-
payload = @udh ? @udh + @short_message : @short_message
|
30
|
+
payload = @udh ? @udh.to_s + @short_message : @short_message
|
31
31
|
@sm_length = payload.length
|
32
32
|
|
33
33
|
#fields set for delivery report
|
@@ -46,6 +46,18 @@ class Smpp::Pdu::DeliverSm < Smpp::Pdu::Base
|
|
46
46
|
super(DELIVER_SM, 0, seq, pdu_body)
|
47
47
|
end
|
48
48
|
|
49
|
+
def total_parts
|
50
|
+
@udh ? @udh[4] : 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def part
|
54
|
+
@udh ? @udh[5] : 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def message_id
|
58
|
+
@udh ? @udh[3] : 0
|
59
|
+
end
|
60
|
+
|
49
61
|
def self.from_wire_data(seq, status, body)
|
50
62
|
options = {}
|
51
63
|
# brutally unpack it
|
@@ -85,7 +97,10 @@ class Smpp::Pdu::DeliverSm < Smpp::Pdu::Base
|
|
85
97
|
end
|
86
98
|
end
|
87
99
|
|
88
|
-
|
100
|
+
# Check to see if body has a 5 bit header
|
101
|
+
if short_message.unpack("c")[0] == 5
|
102
|
+
options[:udh] = short_message.slice!(0..5).unpack("CCCCCC")
|
103
|
+
end
|
89
104
|
|
90
105
|
#Note: if the SM is a delivery receipt (esm_class=4) then the short_message _may_ be in this format:
|
91
106
|
# "id:Smsc2013 sub:1 dlvrd:1 submit date:0610171515 done date:0610171515 stat:0 err:0 text:blah"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
# The SMPP Receiver maintains a unidirectional connection to an SMSC.
|
3
|
+
# Provide a config hash with connection options to get started.
|
4
|
+
# See the sample_gateway.rb for examples of config values.
|
5
|
+
# The receiver accepts a delegate object that may implement
|
6
|
+
# the following (all optional) methods:
|
7
|
+
#
|
8
|
+
# mo_received(receiver, pdu)
|
9
|
+
# delivery_report_received(receiver, pdu)
|
10
|
+
# bound(receiver)
|
11
|
+
# unbound(receiver)
|
12
|
+
|
13
|
+
class Smpp::Receiver < Smpp::Base
|
14
|
+
|
15
|
+
def initialize(config, delegate)
|
16
|
+
super(config)
|
17
|
+
@delegate = delegate
|
18
|
+
|
19
|
+
ed = @config[:enquire_link_delay_secs] || 5
|
20
|
+
comm_inactivity_timeout = 2 * ed
|
21
|
+
rescue Exception => ex
|
22
|
+
logger.error "Exception setting up receiver: #{ex} at #{ex.backtrace.join("\n")}"
|
23
|
+
raise
|
24
|
+
end
|
25
|
+
|
26
|
+
# a PDU is received. Parse it and invoke delegate methods.
|
27
|
+
def process_pdu(pdu)
|
28
|
+
case pdu
|
29
|
+
when Pdu::DeliverSm
|
30
|
+
write_pdu(Pdu::DeliverSmResponse.new(pdu.sequence_number))
|
31
|
+
logger.debug "ESM CLASS #{pdu.esm_class}"
|
32
|
+
if pdu.esm_class != 4
|
33
|
+
# MO message
|
34
|
+
if @delegate.respond_to?(:mo_received)
|
35
|
+
@delegate.mo_received(self, pdu)
|
36
|
+
end
|
37
|
+
else
|
38
|
+
# Delivery report
|
39
|
+
if @delegate.respond_to?(:delivery_report_received)
|
40
|
+
@delegate.delivery_report_received(self, pdu)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
when Pdu::BindReceiverResponse
|
44
|
+
case pdu.command_status
|
45
|
+
when Pdu::Base::ESME_ROK
|
46
|
+
logger.debug "Bound OK."
|
47
|
+
@state = :bound
|
48
|
+
if @delegate.respond_to?(:bound)
|
49
|
+
@delegate.bound(self)
|
50
|
+
end
|
51
|
+
when Pdu::Base::ESME_RINVPASWD
|
52
|
+
logger.warn "Invalid password."
|
53
|
+
# scheduele the connection to close, which eventually will cause the unbound() delegate
|
54
|
+
# method to be invoked.
|
55
|
+
close_connection
|
56
|
+
when Pdu::Base::ESME_RINVSYSID
|
57
|
+
logger.warn "Invalid system id."
|
58
|
+
close_connection
|
59
|
+
else
|
60
|
+
logger.warn "Unexpected BindReceiverResponse. Command status: #{pdu.command_status}"
|
61
|
+
close_connection
|
62
|
+
end
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Send BindReceiverResponse PDU.
|
69
|
+
def send_bind
|
70
|
+
raise IOError, 'Receiver already bound.' unless unbound?
|
71
|
+
pdu = Pdu::BindReceiver.new(
|
72
|
+
@config[:system_id],
|
73
|
+
@config[:password],
|
74
|
+
@config[:system_type],
|
75
|
+
@config[:source_ton],
|
76
|
+
@config[:source_npi],
|
77
|
+
@config[:source_address_range])
|
78
|
+
write_pdu(pdu)
|
79
|
+
end
|
80
|
+
end
|
data/ruby-smpp.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{ruby-smpp}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ray Krueger", "August Z. Flatby"]
|
12
|
-
s.date = %q{
|
12
|
+
s.date = %q{2011-01-06}
|
13
13
|
s.description = %q{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.}
|
14
14
|
s.email = %q{raykrueger@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -21,10 +21,13 @@ Gem::Specification.new do |s|
|
|
21
21
|
".gitignore",
|
22
22
|
"CHANGELOG",
|
23
23
|
"CONTRIBUTORS.txt",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
24
26
|
"LICENSE",
|
25
27
|
"README.rdoc",
|
26
28
|
"Rakefile",
|
27
29
|
"VERSION",
|
30
|
+
"config/environment.rb",
|
28
31
|
"examples/PDU1.example",
|
29
32
|
"examples/PDU2.example",
|
30
33
|
"examples/sample_gateway.rb",
|
@@ -34,6 +37,8 @@ Gem::Specification.new do |s|
|
|
34
37
|
"lib/smpp/optional_parameter.rb",
|
35
38
|
"lib/smpp/pdu/base.rb",
|
36
39
|
"lib/smpp/pdu/bind_base.rb",
|
40
|
+
"lib/smpp/pdu/bind_receiver.rb",
|
41
|
+
"lib/smpp/pdu/bind_receiver_response.rb",
|
37
42
|
"lib/smpp/pdu/bind_resp_base.rb",
|
38
43
|
"lib/smpp/pdu/bind_transceiver.rb",
|
39
44
|
"lib/smpp/pdu/bind_transceiver_response.rb",
|
@@ -48,11 +53,17 @@ Gem::Specification.new do |s|
|
|
48
53
|
"lib/smpp/pdu/submit_sm_response.rb",
|
49
54
|
"lib/smpp/pdu/unbind.rb",
|
50
55
|
"lib/smpp/pdu/unbind_response.rb",
|
56
|
+
"lib/smpp/receiver.rb",
|
51
57
|
"lib/smpp/server.rb",
|
52
58
|
"lib/smpp/transceiver.rb",
|
53
59
|
"lib/sms.rb",
|
54
60
|
"ruby-smpp.gemspec",
|
61
|
+
"test/delegate.rb",
|
55
62
|
"test/optional_parameter_test.rb",
|
63
|
+
"test/pdu_parsing_test.rb",
|
64
|
+
"test/receiver_test.rb",
|
65
|
+
"test/responsive_delegate.rb",
|
66
|
+
"test/server.rb",
|
56
67
|
"test/smpp_test.rb",
|
57
68
|
"test/submit_sm_test.rb"
|
58
69
|
]
|
@@ -63,7 +74,12 @@ Gem::Specification.new do |s|
|
|
63
74
|
s.rubygems_version = %q{1.3.6}
|
64
75
|
s.summary = %q{Ruby implementation of the SMPP protocol, based on EventMachine.}
|
65
76
|
s.test_files = [
|
66
|
-
"test/
|
77
|
+
"test/delegate.rb",
|
78
|
+
"test/optional_parameter_test.rb",
|
79
|
+
"test/pdu_parsing_test.rb",
|
80
|
+
"test/receiver_test.rb",
|
81
|
+
"test/responsive_delegate.rb",
|
82
|
+
"test/server.rb",
|
67
83
|
"test/smpp_test.rb",
|
68
84
|
"test/submit_sm_test.rb",
|
69
85
|
"examples/sample_gateway.rb",
|
data/test/delegate.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# the delagate receives callbacks when interesting things happen on the connection
|
2
|
+
class Delegate
|
3
|
+
|
4
|
+
def mo_received(transceiver, pdu)
|
5
|
+
puts "** mo_received"
|
6
|
+
end
|
7
|
+
|
8
|
+
def delivery_report_received(transceiver, pdu)
|
9
|
+
puts "** delivery_report_received"
|
10
|
+
end
|
11
|
+
|
12
|
+
def message_accepted(transceiver, mt_message_id, pdu)
|
13
|
+
puts "** message_sent"
|
14
|
+
end
|
15
|
+
|
16
|
+
def message_rejected(transceiver, mt_message_id, pdu)
|
17
|
+
puts "** message_rejected"
|
18
|
+
end
|
19
|
+
|
20
|
+
def bound(transceiver)
|
21
|
+
puts "** bound"
|
22
|
+
end
|
23
|
+
|
24
|
+
def unbound(transceiver)
|
25
|
+
puts "** unbound"
|
26
|
+
EventMachine::stop_event_loop
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "../../lib/smpp")
|
4
|
+
|
5
|
+
class PduParsingTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
def test_recieve_single_message
|
8
|
+
raw_data = <<-EOF
|
9
|
+
0000 003d 0000 0005 0000 0000 0000 0002
|
10
|
+
0001 0134 3437 3830 3330 3239 3833 3700
|
11
|
+
0101 3434 3738 3033 3032 3938 3337 0000
|
12
|
+
0000 0000 0000 0000 0454 6573 74
|
13
|
+
EOF
|
14
|
+
|
15
|
+
pdu = create_pdu(raw_data)
|
16
|
+
assert_equal Smpp::Pdu::DeliverSm, pdu.class
|
17
|
+
assert_equal "447803029837", pdu.source_addr
|
18
|
+
assert_equal "447803029837", pdu.destination_addr
|
19
|
+
assert_nil pdu.udh
|
20
|
+
assert_equal "Test", pdu.short_message
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_recieve_part_one_of_multi_part_message
|
24
|
+
part_one_message = <<-EOF
|
25
|
+
0000 00d8 0000 0005 0000 0000 0000 0001
|
26
|
+
0001 0134 3437 3937 3334 3238 3634 3400
|
27
|
+
0101 3434 3739 3736 3232 3430 3137 0000
|
28
|
+
0000 0000 0000 0000 9f05 0003 b402 0154
|
29
|
+
6869 7320 6973 2061 206c 6f6e 6720 6d65
|
30
|
+
7373 6167 6520 746f 2074 6573 7420 7768
|
31
|
+
6574 6865 7220 6f72 206e 6f74 2077 6520
|
32
|
+
6765 7420 7468 6520 6865 6164 6572 2069
|
33
|
+
6e66 6f20 7669 6120 7468 6520 534d 5343
|
34
|
+
2074 6861 7420 7765 2077 6f75 6c64 2072
|
35
|
+
6571 7569 7265 2074 6f20 6265 2061 626c
|
36
|
+
6520 746f 2072 6563 6f6d 706f 7365 206c
|
37
|
+
6f6e 6720 6d65 7373 6167 6573 2069 6e20
|
38
|
+
6861 7368 626c 7565
|
39
|
+
EOF
|
40
|
+
|
41
|
+
pdu = create_pdu(part_one_message)
|
42
|
+
assert_equal Smpp::Pdu::DeliverSm, pdu.class
|
43
|
+
assert_equal "447973428644", pdu.source_addr
|
44
|
+
assert_equal "447976224017", pdu.destination_addr
|
45
|
+
assert_equal [5, 0, 3, 180, 2, 1], pdu.udh
|
46
|
+
|
47
|
+
assert_equal 2, pdu.total_parts, "Have total parts of the message"
|
48
|
+
assert_equal 1, pdu.part, "Correctly show the part"
|
49
|
+
assert_equal 180, pdu.message_id
|
50
|
+
|
51
|
+
assert_equal "This is a long message to test whether or not we get the header info via the SMSC that we would require to be able to recompose long messages in hashblue", pdu.short_message
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_recieve_part_two_of_multi_part_message
|
55
|
+
part_one_message = <<-EOF
|
56
|
+
0000 0062 0000 0005 0000 0000 0000 0002
|
57
|
+
0001 0134 3437 3937 3334 3238 3634 3400
|
58
|
+
0101 3434 3739 3736 3232 3430 3137 0000
|
59
|
+
0000 0000 0000 0000 2905 0003 b402 0220
|
60
|
+
616e 6420 7072 6f76 6964 6520 6120 676f
|
61
|
+
6f64 2075 7365 7220 6578 7065 7269 656e
|
62
|
+
6365
|
63
|
+
EOF
|
64
|
+
|
65
|
+
pdu = create_pdu(part_one_message)
|
66
|
+
assert_equal Smpp::Pdu::DeliverSm, pdu.class
|
67
|
+
assert_equal "447973428644", pdu.source_addr
|
68
|
+
assert_equal "447976224017", pdu.destination_addr
|
69
|
+
assert_equal [5, 0, 3, 180, 2, 2], pdu.udh
|
70
|
+
|
71
|
+
assert_equal 2, pdu.total_parts, "Have total parts of the message"
|
72
|
+
assert_equal 2, pdu.part, "Correctly show the part"
|
73
|
+
assert_equal 180, pdu.message_id
|
74
|
+
|
75
|
+
assert_equal " and provide a good user experience", pdu.short_message
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
def create_pdu(raw_data)
|
80
|
+
hex_data = [raw_data.chomp.gsub(" ","").gsub(/\n/,"")].pack("H*")
|
81
|
+
Smpp::Pdu::Base.create(hex_data)
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
@@ -0,0 +1,197 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
require 'smpp'
|
4
|
+
|
5
|
+
class ReceiverTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
class RecordingDelegate
|
8
|
+
attr_reader :received_pdus, :received_delivery_report_pdus, :states
|
9
|
+
def initialize
|
10
|
+
@received_pdus, @received_delivery_report_pdus, @states = [], [], []
|
11
|
+
end
|
12
|
+
def mo_received(receiver, pdu)
|
13
|
+
@received_pdus << pdu
|
14
|
+
end
|
15
|
+
def delivery_report_received(receiver, pdu)
|
16
|
+
@received_delivery_report_pdus << pdu
|
17
|
+
end
|
18
|
+
def bound(receiver)
|
19
|
+
@states << :bound
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_receiving_bind_receiver_response_with_ok_status_should_become_bound
|
24
|
+
receiver = build_receiver
|
25
|
+
bind_receiver_response = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_ROK, 1)
|
26
|
+
|
27
|
+
receiver.process_pdu(bind_receiver_response)
|
28
|
+
|
29
|
+
assert receiver.bound?
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_receiving_bind_receiver_response_with_ok_status_should_invoke_bound_on_delegate
|
33
|
+
delegate = RecordingDelegate.new
|
34
|
+
receiver = build_receiver(delegate)
|
35
|
+
bind_receiver_response = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_ROK, 1)
|
36
|
+
|
37
|
+
receiver.process_pdu(bind_receiver_response)
|
38
|
+
|
39
|
+
assert_equal [:bound], delegate.states
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_receiving_bind_receiver_response_with_ok_status_should_not_error_if_method_doesnt_exist_on_delegate
|
43
|
+
delegate = Object.new
|
44
|
+
receiver = build_receiver(delegate)
|
45
|
+
bind_receiver_response = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_ROK, 1)
|
46
|
+
|
47
|
+
assert_nothing_raised { receiver.process_pdu(bind_receiver_response) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_receiving_bind_receiver_response_with_error_status_should_not_become_bound
|
51
|
+
receiver = build_receiver
|
52
|
+
bind_receiver_response = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_RBINDFAIL, 1)
|
53
|
+
|
54
|
+
receiver.process_pdu(bind_receiver_response)
|
55
|
+
|
56
|
+
assert receiver.unbound?
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_receiving_bind_receiver_response_with_error_status_should_not_invoke_bound_on_delegate
|
60
|
+
delegate = RecordingDelegate.new
|
61
|
+
receiver = build_receiver(delegate)
|
62
|
+
bind_receiver_response = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_RBINDFAIL, 1)
|
63
|
+
|
64
|
+
receiver.process_pdu(bind_receiver_response)
|
65
|
+
|
66
|
+
assert_equal [], delegate.states
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_receiving_bind_receiver_response_with_error_status_should_close_connection
|
70
|
+
receiver = build_receiver
|
71
|
+
bind_receiver_response = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_RBINDFAIL, 1)
|
72
|
+
|
73
|
+
receiver.process_pdu(bind_receiver_response)
|
74
|
+
|
75
|
+
assert_equal 1, receiver.close_connections
|
76
|
+
end
|
77
|
+
|
78
|
+
def test_receiving_deliver_sm_should_send_deliver_sm_response
|
79
|
+
receiver = build_receiver
|
80
|
+
deliver_sm = Smpp::Pdu::DeliverSm.new("from", "to", "message")
|
81
|
+
|
82
|
+
receiver.process_pdu(deliver_sm)
|
83
|
+
|
84
|
+
first_sent_data = receiver.sent_data.first
|
85
|
+
assert_not_nil first_sent_data
|
86
|
+
actual_response = Smpp::Pdu::Base.create(first_sent_data)
|
87
|
+
expected_response = Smpp::Pdu::DeliverSmResponse.new(deliver_sm.sequence_number)
|
88
|
+
assert_equal expected_response.to_human, actual_response.to_human
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_receiving_deliver_sm_should_invoke_mo_received_on_delegate
|
92
|
+
delegate = RecordingDelegate.new
|
93
|
+
receiver = build_receiver(delegate)
|
94
|
+
deliver_sm = Smpp::Pdu::DeliverSm.new("from", "to", "message")
|
95
|
+
|
96
|
+
receiver.process_pdu(deliver_sm)
|
97
|
+
|
98
|
+
first_received_pdu = delegate.received_pdus.first
|
99
|
+
assert_not_nil first_received_pdu
|
100
|
+
assert_equal deliver_sm.to_human, first_received_pdu.to_human
|
101
|
+
end
|
102
|
+
|
103
|
+
def test_receiving_deliver_sm_should_not_error_if_mo_received_method_doesnt_exist_on_delegate
|
104
|
+
delegate = Object.new
|
105
|
+
receiver = build_receiver(delegate)
|
106
|
+
deliver_sm = Smpp::Pdu::DeliverSm.new("from", "to", "message")
|
107
|
+
|
108
|
+
assert_nothing_raised { receiver.process_pdu(deliver_sm) }
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_receiving_deliver_sm_for_esm_class_4_should_invoke_delivery_report_received_on_delegate
|
112
|
+
delegate = RecordingDelegate.new
|
113
|
+
receiver = build_receiver(delegate)
|
114
|
+
deliver_sm = Smpp::Pdu::DeliverSm.new("from", "to", "message", :esm_class => 4)
|
115
|
+
|
116
|
+
receiver.process_pdu(deliver_sm)
|
117
|
+
|
118
|
+
first_received_delivery_report_pdu = delegate.received_delivery_report_pdus.first
|
119
|
+
assert_not_nil first_received_delivery_report_pdu
|
120
|
+
assert_equal deliver_sm.to_human, first_received_delivery_report_pdu.to_human
|
121
|
+
end
|
122
|
+
|
123
|
+
def test_receiving_deliver_sm_should_not_error_if_received_delivery_report_method_doesnt_exist_on_delegate
|
124
|
+
delegate = Object.new
|
125
|
+
receiver = build_receiver(delegate)
|
126
|
+
deliver_sm = Smpp::Pdu::DeliverSm.new("from", "to", "message", :esm_class => 4)
|
127
|
+
|
128
|
+
assert_nothing_raised { receiver.process_pdu(deliver_sm) }
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def build_receiver(delegate = nil)
|
134
|
+
receiver = Smpp::Receiver.new(1, {}, delegate)
|
135
|
+
class << receiver
|
136
|
+
attr_reader :sent_data, :close_connections
|
137
|
+
def send_data(data)
|
138
|
+
@sent_data = (@sent_data || []) + [data]
|
139
|
+
end
|
140
|
+
def close_connection
|
141
|
+
@close_connections = (@close_connections || 0) + 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
receiver
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
require 'server'
|
150
|
+
require 'delegate'
|
151
|
+
|
152
|
+
class SmppTest < Test::Unit::TestCase
|
153
|
+
|
154
|
+
def config
|
155
|
+
Server::config
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_transceiver_should_bind_and_unbind_then_stop
|
159
|
+
EventMachine.run {
|
160
|
+
EventMachine.start_server "localhost", 9000, Server::Unbind
|
161
|
+
EventMachine.connect "localhost", 9000, Smpp::Receiver, config, Delegate.new
|
162
|
+
}
|
163
|
+
# should not hang here: the server's response should have caused the client to terminate
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_bind_receiver
|
167
|
+
pdu1 = Smpp::Pdu::BindReceiver.new(
|
168
|
+
config[:system_id],
|
169
|
+
config[:password],
|
170
|
+
config[:system_type],
|
171
|
+
config[:source_ton],
|
172
|
+
config[:source_npi],
|
173
|
+
config[:source_address_range]
|
174
|
+
)
|
175
|
+
|
176
|
+
pdu2 = Smpp::Pdu::Base.create(pdu1.data)
|
177
|
+
|
178
|
+
assert_instance_of(Smpp::Pdu::BindReceiver, pdu2)
|
179
|
+
assert_equal(pdu1.system_id, pdu2.system_id)
|
180
|
+
assert_equal(pdu1.password, pdu2.password)
|
181
|
+
assert_equal(pdu1.system_type, pdu2.system_type)
|
182
|
+
assert_equal(pdu1.addr_ton, pdu2.addr_ton)
|
183
|
+
assert_equal(pdu1.addr_npi, pdu2.addr_npi)
|
184
|
+
assert_equal(pdu1.address_range, pdu2.address_range)
|
185
|
+
assert_equal(pdu1.sequence_number, pdu2.sequence_number)
|
186
|
+
assert_equal(pdu1.command_status, pdu2.command_status)
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_bind_receiver_response
|
190
|
+
pdu1 = Smpp::Pdu::BindReceiverResponse.new(nil, Smpp::Pdu::Base::ESME_ROK, config[:system_id])
|
191
|
+
pdu2 = Smpp::Pdu::Base.create(pdu1.data)
|
192
|
+
assert_instance_of(Smpp::Pdu::BindReceiverResponse, pdu2)
|
193
|
+
assert_equal(pdu1.system_id, pdu2.system_id)
|
194
|
+
assert_equal(pdu1.sequence_number, pdu2.sequence_number)
|
195
|
+
assert_equal(pdu1.command_status, pdu2.command_status)
|
196
|
+
end
|
197
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
#TODO This should be made prettier with mocha
|
2
|
+
class ResponsiveDelegate
|
3
|
+
attr_reader :seq, :event_counter
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@seq = 0
|
7
|
+
@event_counter = nil
|
8
|
+
end
|
9
|
+
def seq
|
10
|
+
@seq += 1
|
11
|
+
end
|
12
|
+
def count_function
|
13
|
+
func = caller(1)[0].split("`")[1].split("'")[0].to_sym
|
14
|
+
@event_counter = {} unless @event_counter.is_a?(Hash)
|
15
|
+
@event_counter[func] = 0 if @event_counter[func].nil?
|
16
|
+
@event_counter[func]+=1
|
17
|
+
end
|
18
|
+
|
19
|
+
def mo_received(transceiver, pdu)
|
20
|
+
count_function
|
21
|
+
puts "** mo_received"
|
22
|
+
end
|
23
|
+
|
24
|
+
def delivery_report_received(transceiver, pdu)
|
25
|
+
count_function
|
26
|
+
puts "** delivery_report_received"
|
27
|
+
end
|
28
|
+
|
29
|
+
def message_accepted(transceiver, mt_message_id, pdu)
|
30
|
+
count_function
|
31
|
+
puts "** message_sent"
|
32
|
+
#sending messages from delegate to escape making a fake message sender - not nice :(
|
33
|
+
$tx.send_mt(self.seq, 1, 2, "short_message @ message_accepted")
|
34
|
+
end
|
35
|
+
|
36
|
+
def message_rejected(transceiver, mt_message_id, pdu)
|
37
|
+
count_function
|
38
|
+
puts "** message_rejected"
|
39
|
+
$tx.send_mt(self.seq, 1, 2, "short_message @ message_rejected")
|
40
|
+
end
|
41
|
+
|
42
|
+
def bound(transceiver)
|
43
|
+
count_function
|
44
|
+
puts "** bound"
|
45
|
+
$tx.send_mt(self.seq, 1, 2, "short_message @ bound")
|
46
|
+
end
|
47
|
+
|
48
|
+
def unbound(transceiver)
|
49
|
+
count_function
|
50
|
+
puts "** unbound"
|
51
|
+
EventMachine::stop_event_loop
|
52
|
+
end
|
53
|
+
end
|
data/test/server.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# a server which immediately requests the client to unbind
|
2
|
+
module Server
|
3
|
+
def self.config
|
4
|
+
{
|
5
|
+
:host => 'localhost',
|
6
|
+
:port => 2775,
|
7
|
+
:system_id => 'foo',
|
8
|
+
:password => 'bar',
|
9
|
+
:system_type => '',
|
10
|
+
:source_ton => 0,
|
11
|
+
:source_npi => 1,
|
12
|
+
:destination_ton => 1,
|
13
|
+
:destination_npi => 1,
|
14
|
+
:source_address_range => '',
|
15
|
+
:destination_address_range => ''
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
module Unbind
|
20
|
+
def receive_data(data)
|
21
|
+
send_data Smpp::Pdu::Unbind.new.data
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
module SubmitSmResponse
|
26
|
+
def receive_data(data)
|
27
|
+
# problem: our Pdu's should have factory methods for "both ways"; ie. when created
|
28
|
+
# by client, and when created from wire data.
|
29
|
+
send_data Smpp::Pdu::SubmitSmResponse.new(1, 2, "100").data
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module SubmitSmResponseWithErrorStatus
|
34
|
+
attr_reader :state #state=nil => bind => state=bound => send =>state=sent => unbind => state=unbound
|
35
|
+
def receive_data(data)
|
36
|
+
if @state.nil?
|
37
|
+
@state = 'bound'
|
38
|
+
pdu = Smpp::Pdu::Base.create(data)
|
39
|
+
response_pdu = Smpp::Pdu::BindTransceiverResponse.new(pdu.sequence_number,Smpp::Pdu::Base::ESME_ROK,Server::config[:system_id])
|
40
|
+
send_data response_pdu.data
|
41
|
+
elsif @state == 'bound'
|
42
|
+
@state = 'sent'
|
43
|
+
pdu = Smpp::Pdu::Base.create(data)
|
44
|
+
pdu.to_human
|
45
|
+
send_data Smpp::Pdu::SubmitSmResponse.new(pdu.sequence_number, Smpp::Pdu::Base::ESME_RINVDSTADR, pdu.body).data
|
46
|
+
#send_data Smpp::Pdu::SubmitSmResponse.new(1, 2, "100").data
|
47
|
+
elsif @state == 'sent'
|
48
|
+
@state = 'unbound'
|
49
|
+
send_data Smpp::Pdu::Unbind.new.data
|
50
|
+
else
|
51
|
+
raise "unexpected state"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
data/test/smpp_test.rb
CHANGED
@@ -1,147 +1,9 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'test/unit'
|
3
3
|
require 'smpp'
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
def self.config
|
8
|
-
{
|
9
|
-
:host => 'localhost',
|
10
|
-
:port => 2775,
|
11
|
-
:system_id => 'foo',
|
12
|
-
:password => 'bar',
|
13
|
-
:system_type => '',
|
14
|
-
:source_ton => 0,
|
15
|
-
:source_npi => 1,
|
16
|
-
:destination_ton => 1,
|
17
|
-
:destination_npi => 1,
|
18
|
-
:source_address_range => '',
|
19
|
-
:destination_address_range => ''
|
20
|
-
}
|
21
|
-
end
|
22
|
-
|
23
|
-
module Unbind
|
24
|
-
def receive_data(data)
|
25
|
-
send_data Smpp::Pdu::Unbind.new.data
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
module SubmitSmResponse
|
30
|
-
def receive_data(data)
|
31
|
-
# problem: our Pdu's should have factory methods for "both ways"; ie. when created
|
32
|
-
# by client, and when created from wire data.
|
33
|
-
send_data Smpp::Pdu::SubmitSmResponse.new(1, 2, "100").data
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
module SubmitSmResponseWithErrorStatus
|
38
|
-
attr_reader :state #state=nil => bind => state=bound => send =>state=sent => unbind => state=unbound
|
39
|
-
def receive_data(data)
|
40
|
-
if @state.nil?
|
41
|
-
@state = 'bound'
|
42
|
-
pdu = Smpp::Pdu::Base.create(data)
|
43
|
-
response_pdu = Smpp::Pdu::BindTransceiverResponse.new(pdu.sequence_number,Smpp::Pdu::Base::ESME_ROK,Server::config[:system_id])
|
44
|
-
send_data response_pdu.data
|
45
|
-
elsif @state == 'bound'
|
46
|
-
@state = 'sent'
|
47
|
-
pdu = Smpp::Pdu::Base.create(data)
|
48
|
-
pdu.to_human
|
49
|
-
send_data Smpp::Pdu::SubmitSmResponse.new(pdu.sequence_number, Smpp::Pdu::Base::ESME_RINVDSTADR, pdu.body).data
|
50
|
-
#send_data Smpp::Pdu::SubmitSmResponse.new(1, 2, "100").data
|
51
|
-
elsif @state == 'sent'
|
52
|
-
@state = 'unbound'
|
53
|
-
send_data Smpp::Pdu::Unbind.new.data
|
54
|
-
else
|
55
|
-
raise "unexpected state"
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
end
|
61
|
-
|
62
|
-
|
63
|
-
# the delagate receives callbacks when interesting things happen on the connection
|
64
|
-
class Delegate
|
65
|
-
|
66
|
-
def mo_received(transceiver, pdu)
|
67
|
-
puts "** mo_received"
|
68
|
-
end
|
69
|
-
|
70
|
-
def delivery_report_received(transceiver, pdu)
|
71
|
-
puts "** delivery_report_received"
|
72
|
-
end
|
73
|
-
|
74
|
-
def message_accepted(transceiver, mt_message_id, pdu)
|
75
|
-
puts "** message_sent"
|
76
|
-
end
|
77
|
-
|
78
|
-
def message_rejected(transceiver, mt_message_id, pdu)
|
79
|
-
puts "** message_rejected"
|
80
|
-
end
|
81
|
-
|
82
|
-
def bound(transceiver)
|
83
|
-
puts "** bound"
|
84
|
-
end
|
85
|
-
|
86
|
-
def unbound(transceiver)
|
87
|
-
puts "** unbound"
|
88
|
-
EventMachine::stop_event_loop
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
#TODO This should be made prettier with mocha
|
93
|
-
class ResponsiveDelegate
|
94
|
-
attr_reader :seq, :event_counter
|
95
|
-
|
96
|
-
def initialize
|
97
|
-
@seq = 0
|
98
|
-
@event_counter = nil
|
99
|
-
end
|
100
|
-
def seq
|
101
|
-
@seq += 1
|
102
|
-
end
|
103
|
-
def count_function
|
104
|
-
func = caller(1)[0].split("`")[1].split("'")[0].to_sym
|
105
|
-
@event_counter = {} unless @event_counter.is_a?(Hash)
|
106
|
-
@event_counter[func] = 0 if @event_counter[func].nil?
|
107
|
-
@event_counter[func]+=1
|
108
|
-
end
|
109
|
-
|
110
|
-
def mo_received(transceiver, pdu)
|
111
|
-
count_function
|
112
|
-
puts "** mo_received"
|
113
|
-
end
|
114
|
-
|
115
|
-
def delivery_report_received(transceiver, pdu)
|
116
|
-
count_function
|
117
|
-
puts "** delivery_report_received"
|
118
|
-
end
|
119
|
-
|
120
|
-
def message_accepted(transceiver, mt_message_id, pdu)
|
121
|
-
count_function
|
122
|
-
puts "** message_sent"
|
123
|
-
#sending messages from delegate to escape making a fake message sender - not nice :(
|
124
|
-
$tx.send_mt(self.seq, 1, 2, "short_message @ message_accepted")
|
125
|
-
end
|
126
|
-
|
127
|
-
def message_rejected(transceiver, mt_message_id, pdu)
|
128
|
-
count_function
|
129
|
-
puts "** message_rejected"
|
130
|
-
$tx.send_mt(self.seq, 1, 2, "short_message @ message_rejected")
|
131
|
-
end
|
132
|
-
|
133
|
-
def bound(transceiver)
|
134
|
-
count_function
|
135
|
-
puts "** bound"
|
136
|
-
$tx.send_mt(self.seq, 1, 2, "short_message @ bound")
|
137
|
-
end
|
138
|
-
|
139
|
-
def unbound(transceiver)
|
140
|
-
count_function
|
141
|
-
puts "** unbound"
|
142
|
-
EventMachine::stop_event_loop
|
143
|
-
end
|
144
|
-
end
|
4
|
+
require 'server'
|
5
|
+
require 'delegate'
|
6
|
+
require 'responsive_delegate'
|
145
7
|
|
146
8
|
class Poller
|
147
9
|
def start
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
7
|
+
- 4
|
8
8
|
- 0
|
9
|
-
version: 0.
|
9
|
+
version: 0.4.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ray Krueger
|
@@ -15,13 +15,13 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-01-06 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
name: eventmachine
|
23
|
-
|
24
|
-
|
23
|
+
type: :runtime
|
24
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
@@ -30,8 +30,8 @@ dependencies:
|
|
30
30
|
- 10
|
31
31
|
- 0
|
32
32
|
version: 0.10.0
|
33
|
-
|
34
|
-
|
33
|
+
prerelease: false
|
34
|
+
requirement: *id001
|
35
35
|
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.
|
36
36
|
email: raykrueger@gmail.com
|
37
37
|
executables: []
|
@@ -46,10 +46,13 @@ files:
|
|
46
46
|
- .gitignore
|
47
47
|
- CHANGELOG
|
48
48
|
- CONTRIBUTORS.txt
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
49
51
|
- LICENSE
|
50
52
|
- README.rdoc
|
51
53
|
- Rakefile
|
52
54
|
- VERSION
|
55
|
+
- config/environment.rb
|
53
56
|
- examples/PDU1.example
|
54
57
|
- examples/PDU2.example
|
55
58
|
- examples/sample_gateway.rb
|
@@ -59,6 +62,8 @@ files:
|
|
59
62
|
- lib/smpp/optional_parameter.rb
|
60
63
|
- lib/smpp/pdu/base.rb
|
61
64
|
- lib/smpp/pdu/bind_base.rb
|
65
|
+
- lib/smpp/pdu/bind_receiver.rb
|
66
|
+
- lib/smpp/pdu/bind_receiver_response.rb
|
62
67
|
- lib/smpp/pdu/bind_resp_base.rb
|
63
68
|
- lib/smpp/pdu/bind_transceiver.rb
|
64
69
|
- lib/smpp/pdu/bind_transceiver_response.rb
|
@@ -73,11 +78,17 @@ files:
|
|
73
78
|
- lib/smpp/pdu/submit_sm_response.rb
|
74
79
|
- lib/smpp/pdu/unbind.rb
|
75
80
|
- lib/smpp/pdu/unbind_response.rb
|
81
|
+
- lib/smpp/receiver.rb
|
76
82
|
- lib/smpp/server.rb
|
77
83
|
- lib/smpp/transceiver.rb
|
78
84
|
- lib/sms.rb
|
79
85
|
- ruby-smpp.gemspec
|
86
|
+
- test/delegate.rb
|
80
87
|
- test/optional_parameter_test.rb
|
88
|
+
- test/pdu_parsing_test.rb
|
89
|
+
- test/receiver_test.rb
|
90
|
+
- test/responsive_delegate.rb
|
91
|
+
- test/server.rb
|
81
92
|
- test/smpp_test.rb
|
82
93
|
- test/submit_sm_test.rb
|
83
94
|
has_rdoc: true
|
@@ -111,7 +122,12 @@ signing_key:
|
|
111
122
|
specification_version: 3
|
112
123
|
summary: Ruby implementation of the SMPP protocol, based on EventMachine.
|
113
124
|
test_files:
|
125
|
+
- test/delegate.rb
|
114
126
|
- test/optional_parameter_test.rb
|
127
|
+
- test/pdu_parsing_test.rb
|
128
|
+
- test/receiver_test.rb
|
129
|
+
- test/responsive_delegate.rb
|
130
|
+
- test/server.rb
|
115
131
|
- test/smpp_test.rb
|
116
132
|
- test/submit_sm_test.rb
|
117
133
|
- examples/sample_gateway.rb
|