anjlab-ruby-smpp 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. data/CHANGELOG +52 -0
  2. data/CONTRIBUTORS.txt +11 -0
  3. data/Gemfile +8 -0
  4. data/Gemfile.lock +18 -0
  5. data/LICENSE +20 -0
  6. data/README.rdoc +89 -0
  7. data/Rakefile +53 -0
  8. data/VERSION +1 -0
  9. data/config/environment.rb +2 -0
  10. data/examples/PDU1.example +26 -0
  11. data/examples/PDU2.example +26 -0
  12. data/examples/sample_gateway.rb +137 -0
  13. data/examples/sample_smsc.rb +102 -0
  14. data/lib/smpp.rb +25 -0
  15. data/lib/smpp/base.rb +308 -0
  16. data/lib/smpp/encoding/utf8_encoder.rb +37 -0
  17. data/lib/smpp/optional_parameter.rb +35 -0
  18. data/lib/smpp/pdu/base.rb +183 -0
  19. data/lib/smpp/pdu/bind_base.rb +25 -0
  20. data/lib/smpp/pdu/bind_receiver.rb +4 -0
  21. data/lib/smpp/pdu/bind_receiver_response.rb +4 -0
  22. data/lib/smpp/pdu/bind_resp_base.rb +17 -0
  23. data/lib/smpp/pdu/bind_transceiver.rb +4 -0
  24. data/lib/smpp/pdu/bind_transceiver_response.rb +4 -0
  25. data/lib/smpp/pdu/deliver_sm.rb +142 -0
  26. data/lib/smpp/pdu/deliver_sm_response.rb +12 -0
  27. data/lib/smpp/pdu/enquire_link.rb +11 -0
  28. data/lib/smpp/pdu/enquire_link_response.rb +11 -0
  29. data/lib/smpp/pdu/generic_nack.rb +20 -0
  30. data/lib/smpp/pdu/submit_multi.rb +68 -0
  31. data/lib/smpp/pdu/submit_multi_response.rb +49 -0
  32. data/lib/smpp/pdu/submit_sm.rb +91 -0
  33. data/lib/smpp/pdu/submit_sm_response.rb +31 -0
  34. data/lib/smpp/pdu/unbind.rb +11 -0
  35. data/lib/smpp/pdu/unbind_response.rb +12 -0
  36. data/lib/smpp/receiver.rb +27 -0
  37. data/lib/smpp/server.rb +223 -0
  38. data/lib/smpp/transceiver.rb +109 -0
  39. data/lib/sms.rb +9 -0
  40. data/ruby-smpp.gemspec +96 -0
  41. data/test/delegate.rb +28 -0
  42. data/test/encoding_test.rb +231 -0
  43. data/test/optional_parameter_test.rb +30 -0
  44. data/test/pdu_parsing_test.rb +111 -0
  45. data/test/receiver_test.rb +232 -0
  46. data/test/responsive_delegate.rb +53 -0
  47. data/test/server.rb +56 -0
  48. data/test/smpp_test.rb +239 -0
  49. data/test/submit_sm_test.rb +40 -0
  50. data/test/transceiver_test.rb +35 -0
  51. metadata +133 -0
@@ -0,0 +1,11 @@
1
+ class Smpp::Pdu::EnquireLink < Smpp::Pdu::Base
2
+ handles_cmd ENQUIRE_LINK
3
+
4
+ def initialize(seq = next_sequence_number)
5
+ super(ENQUIRE_LINK, 0, seq)
6
+ end
7
+
8
+ def self.from_wire_data(seq, status, body)
9
+ new(seq)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ class Smpp::Pdu::EnquireLinkResponse < Smpp::Pdu::Base
2
+ handles_cmd ENQUIRE_LINK_RESP
3
+
4
+ def initialize(seq = next_sequence_number)
5
+ super(ENQUIRE_LINK_RESP, ESME_ROK, seq)
6
+ end
7
+
8
+ def self.from_wire_data(seq, status, body)
9
+ new(seq)
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # signals invalid message header
2
+ class Smpp::Pdu::GenericNack < Smpp::Pdu::Base
3
+ handles_cmd GENERIC_NACK
4
+
5
+ attr_accessor :error_code
6
+
7
+ def initialize(seq, error_code, original_sequence_code = nil)
8
+ #TODO: original_sequence_code used to be passed to this function
9
+ #however, a GENERIC_NACK has only one sequence number and no body
10
+ #so this is a useless variable. I leave it here only to preserve
11
+ #the API, but it has no practical use.
12
+ seq ||= next_sequence_number
13
+ super(GENERIC_NACK, error_code, seq)
14
+ @error_code = error_code
15
+ end
16
+
17
+ def self.from_wire_data(seq, status, body)
18
+ new(seq,status,body)
19
+ end
20
+ end
@@ -0,0 +1,68 @@
1
+ # Sending an MT message to multiple addresses
2
+ # Author: Abhishek Parolkar, (abhishek[at]parolkar.com)
3
+ #TODO: Implement from_wire_data for this pdu class.
4
+ class Smpp::Pdu::SubmitMulti < Smpp::Pdu::Base
5
+ IS_SMEADDR = 1 # type of dest_flag
6
+ IS_DISTLISTNAME = 2 #type of dest_flag
7
+
8
+ # Note: short_message (the SMS body) must be in iso-8859-1 format
9
+ def initialize(source_addr, destination_addr_array, short_message, options={})
10
+ options.merge!(
11
+ :esm_class => 0, # default smsc mode
12
+ :dcs => 3 # iso-8859-1
13
+ ) { |key, old_val, new_val| old_val }
14
+
15
+ @msg_body = short_message
16
+
17
+ udh = options[:udh]
18
+ service_type = ''
19
+ source_addr_ton = 0 # network specific
20
+ source_addr_npi = 1 # unknown
21
+ number_of_dests = destination_addr_array.length # Max value can be 254
22
+ dest_addr_ton = 1 # international
23
+ dest_addr_npi = 1 # unknown
24
+ dest_addresses = build_destination_addresses(destination_addr_array,dest_addr_ton,dest_addr_npi,IS_SMEADDR)
25
+ esm_class = options[:esm_class]
26
+ protocol_id = 0
27
+ priority_flag = 0
28
+ schedule_delivery_time = ''
29
+ validity_period = ''
30
+ registered_delivery = 1 # we want delivery notifications
31
+ replace_if_present_flag = 0
32
+ data_coding = options[:dcs]
33
+ sm_default_msg_id = 0
34
+ payload = udh ? udh + short_message : short_message # this used to be (short_message + "\0")
35
+ sm_length = payload.length
36
+
37
+ # craft the string/byte buffer
38
+ pdu_body = sprintf("%s\0%c%c%s\0%c%s\0%c%c%c%s\0%s\0%c%c%c%c%c%s", service_type, source_addr_ton, source_addr_npi, source_addr, number_of_dests,dest_addresses, esm_class, protocol_id, priority_flag, schedule_delivery_time, validity_period,
39
+ registered_delivery, replace_if_present_flag, data_coding, sm_default_msg_id, sm_length, payload)
40
+ super(SUBMIT_MULTI, 0, next_sequence_number, pdu_body)
41
+ end
42
+
43
+ # some special formatting is needed for SubmitSm PDUs to show the actual message content
44
+ def to_human
45
+ # convert header (4 bytes) to array of 4-byte ints
46
+ a = @data.to_s.unpack('N4')
47
+ sprintf("(%22s) len=%3d cmd=%8s status=%1d seq=%03d (%s)", self.class.to_s[11..-1], a[0], a[1].to_s(16), a[2], a[3], @msg_body[0..30])
48
+ end
49
+
50
+ def build_destination_addresses(dest_array,dest_addr_ton,dest_addr_npi, dest_flag = IS_SMEADDR)
51
+ formatted_array = Array.new
52
+ dest_array.each { |dest_elem|
53
+ if dest_flag == IS_SMEADDR
54
+ packet_str = sprintf("%c%c%c%s",IS_SMEADDR,dest_addr_ton,dest_addr_npi,dest_elem)
55
+ formatted_array.push(packet_str)
56
+
57
+ elsif dest_flag == IS_DISTLISTNAME
58
+ packet_str = sprintf("%c%s",IS_SMEADDR,dest_elem)
59
+ formatted_array.push(packet_str)
60
+
61
+ end
62
+
63
+ }
64
+
65
+ formatted_array.join("\0");
66
+ end
67
+
68
+ end
@@ -0,0 +1,49 @@
1
+ # Recieving response for an MT message sent to multiple addresses
2
+ # Author: Abhishek Parolkar, (abhishek[at]parolkar.com)
3
+ class Smpp::Pdu::SubmitMultiResponse < Smpp::Pdu::Base
4
+ class UnsuccessfulSme
5
+ Struct.new(:dest_addr_ton, :dest_addr_npi, :destination_addr, :error_status_code)
6
+ end
7
+
8
+ handles_cmd SUBMIT_MULTI_RESP
9
+
10
+ attr_accessor :message_id, :unsuccess_smes
11
+
12
+ def initialize(seq, status, message_id, unsuccess_smes = [])
13
+ @unsuccess_smes = unsuccess_smes
14
+ seq ||= next_sequence_number
15
+
16
+ packed_smes = ""
17
+ unsuccess_smes.each do |sme|
18
+ packed_smes << [
19
+ sme.dest_addr_ton,
20
+ sme.dest_addr_npi,
21
+ sme.destination_addr,
22
+ sme.error_status_code
23
+ ].pack("CCZ*N")
24
+ end
25
+ body = [message_id, unsuccess_smes.size, packed_smes].pack("Z*Ca*")
26
+
27
+ super(SUBMIT_MULTI_RESP, status, seq, body)
28
+ @message_id = message_id
29
+ end
30
+
31
+ def self.from_wire_data(seq, status, body)
32
+ message_id, no_unsuccess, rest = body.unpack("Z*Ca*")
33
+ unsuccess_smes = []
34
+
35
+ no_unsuccess.times do |i|
36
+ #unpack the next sme
37
+ dest_addr_ton, dest_addr_npi, destination_addr, error_status_code =
38
+ rest.unpack("CCZ*N")
39
+ #remove the SME from rest
40
+ rest.slice!(0,7 + destination_addr.length)
41
+ unsuccess_smes << UnsuccessfulSme.new(dest_addr_ton, dest_addr_npi, destination_addr, error_status_code)
42
+ end
43
+
44
+ new(seq, status, message_id, unsuccess_smes)
45
+ end
46
+
47
+
48
+
49
+ end
@@ -0,0 +1,91 @@
1
+ # Sending an MT message
2
+ class Smpp::Pdu::SubmitSm < Smpp::Pdu::Base
3
+ handles_cmd SUBMIT_SM
4
+ attr_reader :service_type, :source_addr_ton, :source_addr_npi, :source_addr, :dest_addr_ton, :dest_addr_npi,
5
+ :destination_addr, :esm_class, :protocol_id, :priority_flag, :schedule_delivery_time,
6
+ :validity_period, :registered_delivery, :replace_if_present_flag, :data_coding,
7
+ :sm_default_msg_id, :sm_length, :udh, :short_message, :optional_parameters
8
+
9
+
10
+ # Note: short_message (the SMS body) must be in iso-8859-1 format
11
+ def initialize(source_addr, destination_addr, short_message, options={}, seq = nil)
12
+
13
+ @msg_body = short_message
14
+
15
+ @udh = options[:udh]
16
+ @service_type = options[:service_type]? options[:service_type] :''
17
+ @source_addr_ton = options[:source_addr_ton]?options[:source_addr_ton]:0 # network specific
18
+ @source_addr_npi = options[:source_addr_npi]?options[:source_addr_npi]:1 # unknown
19
+ @source_addr = source_addr
20
+ @dest_addr_ton = options[:dest_addr_ton]?options[:dest_addr_ton]:1 # international
21
+ @dest_addr_npi = options[:dest_addr_npi]?options[:dest_addr_npi]:1 # unknown
22
+ @destination_addr = destination_addr
23
+ @esm_class = options[:esm_class]?options[:esm_class]:0 # default smsc mode
24
+ @protocol_id = options[:protocol_id]?options[:protocol_id]:0
25
+ @priority_flag = options[:priority_flag]?options[:priority_flag]:0
26
+ @schedule_delivery_time = options[:schedule_delivery_time]?options[:schedule_delivery_time]:''
27
+ @validity_period = options[:validity_period]?options[:validity_period]:''
28
+ @registered_delivery = options[:registered_delivery]?options[:registered_delivery]:1 # we want delivery notifications
29
+ @replace_if_present_flag = options[:replace_if_present_flag]?options[:replace_if_present_flag]:0
30
+ @data_coding = options[:data_coding]?options[:data_coding]:3 # iso-8859-1
31
+ @sm_default_msg_id = options[:sm_default_msg_id]?options[:sm_default_msg_id]:0
32
+ @short_message = short_message
33
+ payload = @udh ? @udh + @short_message : @short_message
34
+ @sm_length = payload.length
35
+
36
+ @optional_parameters = options[:optional_parameters]
37
+
38
+ # craft the string/byte buffer
39
+ pdu_body = sprintf("%s\0%c%c%s\0%c%c%s\0%c%c%c%s\0%s\0%c%c%c%c%c%s", @service_type, @source_addr_ton, @source_addr_npi, @source_addr,
40
+ @dest_addr_ton, @dest_addr_npi, @destination_addr, @esm_class, @protocol_id, @priority_flag, @schedule_delivery_time, @validity_period,
41
+ @registered_delivery, @replace_if_present_flag, @data_coding, @sm_default_msg_id, @sm_length, payload)
42
+
43
+ if @optional_parameters
44
+ pdu_body << optional_parameters_to_buffer(@optional_parameters)
45
+ end
46
+
47
+ seq ||= next_sequence_number
48
+
49
+ super(SUBMIT_SM, 0, seq, pdu_body)
50
+ end
51
+
52
+ # some special formatting is needed for SubmitSm PDUs to show the actual message content
53
+ def to_human
54
+ # convert header (4 bytes) to array of 4-byte ints
55
+ a = @data.to_s.unpack('N4')
56
+ sprintf("(%22s) len=%3d cmd=%8s status=%1d seq=%03d (%s)", self.class.to_s[11..-1], a[0], a[1].to_s(16), a[2], a[3], @msg_body[0..30])
57
+ end
58
+
59
+ def self.from_wire_data(seq, status, body)
60
+ options = {}
61
+
62
+ options[:service_type],
63
+ options[:source_addr_ton],
64
+ options[:source_addr_npi],
65
+ source_addr,
66
+ options[:dest_addr_ton],
67
+ options[:dest_addr_npi],
68
+ destination_addr,
69
+ options[:esm_class],
70
+ options[:protocol_id],
71
+ options[:priority_flag],
72
+ options[:schedule_delivery_time],
73
+ options[:validity_period],
74
+ options[:registered_delivery],
75
+ options[:replace_if_present_flag],
76
+ options[:data_coding],
77
+ options[:sm_default_msg_id],
78
+ options[:sm_length],
79
+ remaining_bytes = body.unpack('Z*CCZ*CCZ*CCCZ*Z*CCCCCa*')
80
+
81
+ short_message = remaining_bytes.slice!(0...options[:sm_length])
82
+
83
+ #everything left in remaining_bytes is 3.4 optional parameters
84
+ options[:optional_parameters] = parse_optional_parameters(remaining_bytes)
85
+
86
+ Smpp::Base.logger.debug "SubmitSM with source_addr=#{source_addr}, destination_addr=#{destination_addr}"
87
+
88
+ new(source_addr, destination_addr, short_message, options, seq)
89
+ end
90
+
91
+ end
@@ -0,0 +1,31 @@
1
+ class Smpp::Pdu::SubmitSmResponse < Smpp::Pdu::Base
2
+ handles_cmd SUBMIT_SM_RESP
3
+
4
+ attr_accessor :message_id
5
+ attr_accessor :optional_parameters
6
+
7
+ def initialize(seq, status, message_id, optional_parameters=nil)
8
+ seq ||= next_sequence_number
9
+ body = message_id.to_s + "\000"
10
+ super(SUBMIT_SM_RESP, status, seq, body)
11
+ @message_id = message_id
12
+ @optional_parameters = optional_parameters
13
+ end
14
+
15
+ def optional_parameter(tag)
16
+ if optional_parameters
17
+ if param = optional_parameters[tag]
18
+ param.value
19
+ end
20
+ end
21
+ end
22
+
23
+ def self.from_wire_data(seq, status, body)
24
+ message_id, remaining_bytes = body.unpack("Z*a*")
25
+ optionals = nil
26
+ if remaining_bytes && !remaining_bytes.empty?
27
+ optionals = parse_optional_parameters(remaining_bytes)
28
+ end
29
+ new(seq, status, message_id, optionals)
30
+ end
31
+ end
@@ -0,0 +1,11 @@
1
+ class Smpp::Pdu::Unbind < Smpp::Pdu::Base
2
+ handles_cmd UNBIND
3
+
4
+ def initialize(seq=next_sequence_number)
5
+ super(UNBIND, 0, seq)
6
+ end
7
+
8
+ def self.from_wire_data(seq, status, body)
9
+ new(seq)
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ class Smpp::Pdu::UnbindResponse < Smpp::Pdu::Base
2
+ handles_cmd UNBIND_RESP
3
+
4
+ def initialize(seq, status)
5
+ seq ||= next_sequence_number
6
+ super(UNBIND_RESP, status, seq)
7
+ end
8
+
9
+ def self.from_wire_data(seq, status, body)
10
+ new(seq, status)
11
+ end
12
+ end
@@ -0,0 +1,27 @@
1
+ # The SMPP Receiver maintains a unidirectional connection to an SMSC.
2
+ # Provide a config hash with connection options to get started.
3
+ # See the sample_gateway.rb for examples of config values.
4
+ # The receiver accepts a delegate object that may implement
5
+ # the following (all optional) methods:
6
+ #
7
+ # mo_received(receiver, pdu)
8
+ # delivery_report_received(receiver, pdu)
9
+ # bound(receiver)
10
+ # unbound(receiver)
11
+
12
+ class Smpp::Receiver < Smpp::Base
13
+
14
+ # Send BindReceiverResponse PDU.
15
+ def send_bind
16
+ raise IOError, 'Receiver already bound.' unless unbound?
17
+ pdu = Pdu::BindReceiver.new(
18
+ @config[:system_id],
19
+ @config[:password],
20
+ @config[:system_type],
21
+ @config[:source_ton],
22
+ @config[:source_npi],
23
+ @config[:source_address_range])
24
+ write_pdu(pdu)
25
+ end
26
+
27
+ end
@@ -0,0 +1,223 @@
1
+ # --------
2
+ # This is experimental stuff submitted by taryn@taryneast.org
3
+ # --------
4
+
5
+ # the opposite of a client-based receiver, the server transmitter will send
6
+ # out MOs to the client when set up
7
+ class Smpp::Server < Smpp::Base
8
+
9
+ attr_accessor :bind_status
10
+
11
+ # Expects a config hash,
12
+ # a proc to invoke for incoming (MO) messages,
13
+ # a proc to invoke for delivery reports,
14
+ # and optionally a hash-like storage for pending delivery reports.
15
+ def initialize(config, received_messages = [], sent_messages = [])
16
+ super(config)
17
+ @state = :unbound
18
+ @received_messages = received_messages
19
+ @sent_messages = sent_messages
20
+
21
+ ed = @config[:enquire_link_delay_secs] || 5
22
+ comm_inactivity_timeout = [ed - 5, 3].max
23
+ rescue Exception => ex
24
+ logger.error "Exception setting up server: #{ex}"
25
+ raise
26
+ end
27
+
28
+
29
+ #######################################################################
30
+ # Session management functions
31
+ #######################################################################
32
+ # Session helpers
33
+
34
+ # convenience methods
35
+ # is this session currently bound?
36
+ def bound?
37
+ @state == :bound
38
+ end
39
+ # is this session currently unbound?
40
+ def unbound?
41
+ @state == :unbound
42
+ end
43
+ # set of valid bind statuses
44
+ BIND_STATUSES = {:transmitter => :bound_tx,
45
+ :receiver => :bound_rx, :transceiver => :bound_trx}
46
+ # set the bind status based on the common-name for the bind class
47
+ def set_bind_status(bind_classname)
48
+ @bind_status = BIND_STATUSES[bind_classname]
49
+ end
50
+ # and kill the bind status when done
51
+ def unset_bind_status
52
+ @bind_status = nil
53
+ end
54
+ # what is the bind_status?
55
+ def bind_status
56
+ @bind_status
57
+ end
58
+ # convenience function - are we able to transmit in this bind-Status?
59
+ def transmitting?
60
+ # not transmitting if not bound
61
+ return false if unbound? || bind_status.nil?
62
+ # receivers can't transmit
63
+ bind_status != :bound_rx
64
+ end
65
+ # convenience function - are we able to receive in this bind-Status?
66
+ def receiving?
67
+ # not receiving if not bound
68
+ return false if unbound? || bind_status.nil?
69
+ # transmitters can't receive
70
+ bind_status != :bound_tx
71
+ end
72
+
73
+ def am_server?
74
+ true
75
+ end
76
+
77
+ # REVISIT - not sure if these are using the correct data. Currently just
78
+ # pulls the data straight out of the given pdu and sends it right back.
79
+ #
80
+ def fetch_bind_response_class(bind_classname)
81
+ # check we have a valid classname - probably overkill as only our code
82
+ # will send the classnames through
83
+ raise IOError, "bind class name missing" if bind_classname.nil?
84
+ raise IOError, "bind class name: #{bind_classname} unknown" unless BIND_STATUSES.has_key?(bind_classname)
85
+
86
+ case bind_classname
87
+ when :transceiver
88
+ return Smpp::Pdu::BindTransceiverResponse
89
+ when :transmitter
90
+ return Smpp::Pdu::BindTransmitterResponse
91
+ when :receiver
92
+ return Smpp::Pdu::BindReceiverResponse
93
+ end
94
+ end
95
+
96
+ # actually perform the action of binding the session to the given session
97
+ # type
98
+ def bind_session(bind_pdu, bind_classname)
99
+ # TODO: probably should not "raise" here - what's better?
100
+ raise IOError, "Session already bound." if bound?
101
+ response_class = fetch_bind_response_class(bind_classname)
102
+
103
+ # TODO: look inside the pdu for the password and check it
104
+
105
+ send_bind_response(bind_pdu, response_class)
106
+
107
+ @state = :bound
108
+ set_bind_status(bind_classname)
109
+ end
110
+
111
+ # Send BindReceiverResponse PDU - used in response to a "bind_receiver"
112
+ # pdu.
113
+ def send_bind_response(bind_pdu, bind_class)
114
+ resp_pdu = bind_class.new(
115
+ bind_pdu.sequence_number,
116
+ # currently assume that it binds ok
117
+ Pdu::Base::ESME_ROK,
118
+ # TODO: not sure where we get the system ID
119
+ # is this the session id?
120
+ bind_pdu.system_id)
121
+ write_pdu(resp_pdu)
122
+ end
123
+
124
+ #######################################################################
125
+ # Message submission (transmitter) functions (used by transmitter and
126
+ # transceiver-bound system)
127
+ # Note - we only support submit_sm message type, not submit_multi or
128
+ # data_sm message types
129
+ #######################################################################
130
+ # Receive an incoming message to send to the network and respond
131
+ # REVISIT = just a stub
132
+ def receive_sm(pdu)
133
+ # TODO: probably should not "raise" here - what's better?
134
+ raise IOError, "Connection not bound." if unbound?
135
+ # Doesn't matter if it's a TX/RX/TRX, have to send a SubmitSmResponse:
136
+ # raise IOError, "Connection not set to receive" unless receiving?
137
+
138
+ # Must respond to SubmitSm requests with the same sequence number
139
+ m_seq = pdu.sequence_number
140
+ # add the id to the list of ids of which we're awaiting acknowledgement
141
+ @received_messages << m_seq
142
+
143
+ # In theory this is where the MC would actually do something useful with
144
+ # the PDU - eg send it on to the network. We'd check if it worked and
145
+ # send a failure PDU if it failed.
146
+ #
147
+ # Given this is a dummy MC, that's not necessary, so all our responses
148
+ # will be OK.
149
+
150
+ # so respond with a successful response
151
+ pdu = Pdu::SubmitSmResponse.new(m_seq, Pdu::Base::ESME_ROK, message_id = '' )
152
+ write_pdu pdu
153
+ @received_messages.delete m_seq
154
+
155
+ logger.info "Received submit sm message: #{m_seq}"
156
+ end
157
+
158
+ #######################################################################
159
+ # Message delivery (receiver) functions (used by receiver and
160
+ # transceiver-bound system)
161
+ #######################################################################
162
+ # When we get an incoming SMS to send on to the client, we need to
163
+ # initiate one of these PDUs.
164
+ # Note - data doesn't have to be valid, as we're not really doing much
165
+ # useful with it. Only the params that will be pulled out by the test
166
+ # system need to be valid.
167
+ def deliver_sm(from, to, message, config = {})
168
+ # TODO: probably should not "raise" here - what's better?
169
+ raise IOError, "Connection not bound." if unbound?
170
+ raise IOError, "Connection not set to receive" unless receiving?
171
+
172
+ # submit the given message
173
+ new_pdu = Pdu::DeliverSm.new(from, to, message, config)
174
+ write_pdu(new_pdu)
175
+ # add the id to the list of ids of which we're awaiting acknowledgement
176
+ @sent_messages << m_seq
177
+
178
+ logger.info "Delivered SM message id: #{m_seq}"
179
+
180
+ new_pdu
181
+ end
182
+
183
+ # Acknowledge delivery of an outgoing MO message
184
+ # REVISIT = just a stub
185
+ def accept_deliver_sm_response(pdu)
186
+ m_seq = pdu.sequence_number
187
+ # add the id to the list of ids we're awaiting acknowledgement of
188
+ # REVISIT - what id do we need to store?
189
+ unless @sent_messages && @sent_messages.include?(m_seq)
190
+ logger.error("Received deliver response for message for which we have no saved id: #{m_seq}")
191
+ else
192
+ @sent_messages.delete(m_seq)
193
+ logger.info "Acknowledged receipt of SM delivery message id: #{m_seq}"
194
+ end
195
+ end
196
+
197
+
198
+ # a PDU is received
199
+ # these pdus are all responses to a message sent by the client and require
200
+ # their own special response
201
+ def process_pdu(pdu)
202
+ case pdu
203
+ # client has asked to set up a connection
204
+ when Pdu::BindTransmitter
205
+ bind_session(pdu, :transmitter)
206
+ when Pdu::BindReceiver
207
+ bind_session(pdu, :receiver)
208
+ when Pdu::BindTransceiver
209
+ bind_session(pdu, :transceiver)
210
+ # client has acknowledged receipt of a message we sent to them
211
+ when Pdu::DeliverSmResponse
212
+ accept_deliver_sm_response(pdu) # acknowledge its sending
213
+
214
+ # client has asked for a message to be sent
215
+ when Pdu::SubmitSm
216
+ receive_sm(pdu)
217
+ else
218
+ # for generic functions or default fallback
219
+ super(pdu)
220
+ end
221
+ end
222
+
223
+ end