Class: Smpp::Base

Inherits:
EventMachine::Connection
  • Object
show all
Includes:
Smpp
Defined in:
lib/smpp/base.rb

Direct Known Subclasses

Receiver, Server, Transceiver, Transmitter

Constant Summary

Constants included from Smpp

VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, delegate) ⇒ Base

Returns a new instance of Base.



15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/smpp/base.rb', line 15

def initialize(config, delegate)
  @state = :unbound
  @config = config
  @data = ""
  @delegate = delegate

  # Array of un-acked MT message IDs indexed by sequence number.
  # As soon as we receive SubmitSmResponse we will use this to find the
  # associated message ID, and then create a pending delivery report.
  @ack_ids = {}

  ed = @config[:enquire_link_delay_secs] || 5
  comm_inactivity_timeout = 2 * ed
end

Instance Attribute Details

#stateObject

:bound or :unbound



13
14
15
# File 'lib/smpp/base.rb', line 13

def state
  @state
end

Class Method Details

.loggerObject



39
40
41
# File 'lib/smpp/base.rb', line 39

def Base.logger
  @@logger
end

.logger=(logger) ⇒ Object



43
44
45
# File 'lib/smpp/base.rb', line 43

def Base.logger=(logger)
  @@logger = logger
end

Instance Method Details

#bound?Boolean

Returns:

  • (Boolean)


35
36
37
# File 'lib/smpp/base.rb', line 35

def bound?
  @state == :bound
end

#loggerObject



47
48
49
# File 'lib/smpp/base.rb', line 47

def logger
  @@logger
end

#post_initObject

invoked by EventMachine when connected



53
54
55
56
57
58
59
60
61
62
# File 'lib/smpp/base.rb', line 53

def post_init
  # send Bind PDU if we are a binder (eg
  # Receiver/Transmitter/Transceiver
  send_bind unless defined?(am_server?) && am_server?

  # start timer that will periodically send enquire link PDUs
  start_enquire_link_timer(@config[:enquire_link_delay_secs]) if @config[:enquire_link_delay_secs]
rescue Exception => ex
  logger.error "Error starting RX: #{ex.message} at #{ex.backtrace[0]}"
end

#process_pdu(pdu) ⇒ Object

process common PDUs returns true if no further processing necessary



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/smpp/base.rb', line 138

def process_pdu(pdu)
  case pdu
  when Pdu::EnquireLinkResponse
    # nop
  when Pdu::EnquireLink
    write_pdu(Pdu::EnquireLinkResponse.new(pdu.sequence_number))
  when Pdu::Unbind
    @state = :unbound
    write_pdu(Pdu::UnbindResponse.new(pdu.sequence_number, Pdu::Base::ESME_ROK))
    close_connection
  when Pdu::UnbindResponse
    logger.info "Unbound OK. Closing connection."
    close_connection
  when Pdu::GenericNack
    logger.warn "Received NACK! (error code #{pdu.error_code})."
    # we don't take this lightly: close the connection
    close_connection
  when Pdu::DeliverSm
    begin
      logger.debug "ESM CLASS #{pdu.esm_class}"
      if pdu.esm_class != 4
        # MO message
        run_callback(:mo_received, self, pdu)
      else
        # Delivery report
        run_callback(:delivery_report_received, self, pdu)
      end
      write_pdu(Pdu::DeliverSmResponse.new(pdu.sequence_number))
    rescue => e
      logger.warn "Send Receiver Temporary App Error due to #{e.inspect} raised in delegate"
      write_pdu(Pdu::DeliverSmResponse.new(pdu.sequence_number, Pdu::Base::ESME_RX_T_APPN))
    end
  when Pdu::BindTransceiverResponse
    case pdu.command_status
    when Pdu::Base::ESME_ROK
      logger.debug "Bound OK."
      @state = :bound
      run_callback(:bound, self)
    when Pdu::Base::ESME_RINVPASWD
      logger.warn "Invalid password."
      # schedule the connection to close, which eventually will cause the unbound() delegate
      # method to be invoked.
      run_callback(:invalid_password, self)
      close_connection
    when Pdu::Base::ESME_RINVSYSID
      logger.warn "Invalid system id."
      run_callback(:invalid_system_id, self)
      close_connection
    else
      logger.warn "Unexpected BindTransceiverResponse. Command status: #{pdu.command_status}"
      run_callback(:unexpected_error, self)
      close_connection
    end
  when Pdu::SubmitSmResponse
    mt_message_id = @ack_ids.delete(pdu.sequence_number)
    if !mt_message_id
      raise "Got SubmitSmResponse for unknown sequence_number: #{pdu.sequence_number}"
    end
    if pdu.command_status != Pdu::Base::ESME_ROK
      logger.error "Error status in SubmitSmResponse: #{pdu.command_status}"
      run_callback(:message_rejected, self, mt_message_id, pdu)
    else
      logger.info "Got OK SubmitSmResponse (#{pdu.message_id} -> #{mt_message_id})"
      run_callback(:message_accepted, self, mt_message_id, pdu)
    end
  when Pdu::SubmitMultiResponse
    mt_message_id = @ack_ids[pdu.sequence_number]
    if !mt_message_id
      raise "Got SubmitMultiResponse for unknown sequence_number: #{pdu.sequence_number}"
    end
    if pdu.command_status != Pdu::Base::ESME_ROK
      logger.error "Error status in SubmitMultiResponse: #{pdu.command_status}"
      run_callback(:message_rejected, self, mt_message_id, pdu)
    else
      logger.info "Got OK SubmitMultiResponse (#{pdu.message_id} -> #{mt_message_id})"
      run_callback(:message_accepted, self, mt_message_id, pdu)
    end
  when Pdu::BindReceiverResponse
    case pdu.command_status
    when Pdu::Base::ESME_ROK
      logger.debug "Bound OK."
      @state = :bound
      run_callback(:bound, self)
    when Pdu::Base::ESME_RINVPASWD
      logger.warn "Invalid password."
      run_callback(:invalid_password, self)
      # scheduele the connection to close, which eventually will cause the unbound() delegate
      # method to be invoked.
      close_connection
    when Pdu::Base::ESME_RINVSYSID
      logger.warn "Invalid system id."
      run_callback(:invalid_system_id, self)
      close_connection
    else
      logger.warn "Unexpected BindReceiverResponse. Command status: #{pdu.command_status}"
      run_callback(:unexpected_error, self)
      close_connection
    end
  when Pdu::BindTransmitterResponse
    case pdu.command_status
    when Pdu::Base::ESME_ROK
      logger.debug "Bound OK."
      @state = :bound
      run_callback(:bound, self)
    when Pdu::Base::ESME_RINVPASWD
      logger.warn "Invalid password."
      run_callback(:invalid_password, self)
      # schedule the connection to close, which eventually will cause the unbound() delegate
      # method to be invoked.
      close_connection
    when Pdu::Base::ESME_RINVSYSID
      logger.warn "Invalid system id."
      run_callback(:invalid_system_id, self)
      close_connection
    else
      logger.warn "Unexpected BindReceiverResponse. Command status: #{pdu.command_status}"
      run_callback(:unexpected_error, self)
      close_connection
    end
  else
    logger.warn "(#{self.class.name}) Received unexpected PDU: #{pdu.to_human}."
    run_callback(:unexpected_pdu, self, pdu)
    close_connection
  end
end

#receive_data(data) ⇒ Object

EventMachine::Connection#receive_data



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/smpp/base.rb', line 91

def receive_data(data)
  #append data to buffer
  @data << data

  while (@data.length >=4)
    cmd_length = @data[0..3].unpack('N').first
    if(@data.length < cmd_length)
      #not complete packet ... break
      break
    end

    pkt = @data.slice!(0,cmd_length)

    begin
      # parse incoming PDU
      pdu = read_pdu(pkt)

      # let subclass process it
      process_pdu(pdu) if pdu
    rescue Exception => e
      logger.error "Error receiving data: #{e}\n#{e.backtrace.join("\n")}"
      run_callback(:data_error, e)
    end

  end
end

#run_callback(cb, *args) ⇒ Object



130
131
132
133
134
# File 'lib/smpp/base.rb', line 130

def run_callback(cb, *args)
  if @delegate.respond_to?(cb)
    @delegate.send(cb, *args)
  end
end

#send_unbindObject



125
126
127
128
# File 'lib/smpp/base.rb', line 125

def send_unbind
  write_pdu Pdu::Unbind.new
  @state = :unbound
end

sets up a periodic timer that will periodically enquire as to the state of the connection Note: to add in custom executable code (that only runs on an open connection), derive from the appropriate Smpp class and overload the method named: periodic_call_method



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/smpp/base.rb', line 69

def start_enquire_link_timer(delay_secs)
  logger.info "Starting enquire link timer (with #{delay_secs}s interval)"
  timer = EventMachine::PeriodicTimer.new(delay_secs) do
    if error?
      logger.warn "Link timer: Connection is in error state. Disconnecting."
      timer.cancel
      close_connection
    elsif unbound?
      logger.warn "Link is unbound, waiting until next #{delay_secs} interval before querying again"
    else

      # if the user has defined a method to be called periodically, do
      # it now - and continue if it indicates to do so
      rval = defined?(periodic_call_method) ? periodic_call_method : true

      # only send an OK if this worked
      write_pdu Pdu::EnquireLink.new if rval
    end
  end
end

#unbindObject

EventMachine::Connection#unbind Invoked by EM when connection is closed. Delegates should consider breaking the event loop and reconnect when they receive this callback.



121
122
123
# File 'lib/smpp/base.rb', line 121

def unbind
  run_callback(:unbound, self)
end

#unbound?Boolean

queries the state of the transmitter - is it bound?

Returns:

  • (Boolean)


31
32
33
# File 'lib/smpp/base.rb', line 31

def unbound?
  @state == :unbound
end