anjlab-ruby-smpp 0.6.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.
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