Class: Smpp::Base

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

Direct Known Subclasses

Receiver, Server, Transceiver

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config, delegate) ⇒ Base

Returns a new instance of Base.



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

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



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

def state
  @state
end

Class Method Details

.loggerObject



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

def Base.logger
  @@logger
end

.logger=(logger) ⇒ Object



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

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

Instance Method Details

#bound?Boolean

Returns:

  • (Boolean)


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

def bound?
  @state == :bound
end

#loggerObject



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

def logger
  @@logger
end

#post_initObject

invoked by EventMachine when connected



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

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



136
137
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
# File 'lib/smpp/base.rb', line 136

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
        if @delegate.respond_to?(:mo_received)
          @delegate.mo_received(self, pdu)
        end
      else
        # Delivery report
        if @delegate.respond_to?(:delivery_report_received)
          @delegate.delivery_report_received(self, pdu)
        end
      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
      if @delegate.respond_to?(:bound)
        @delegate.bound(self)
      end
    when Pdu::Base::ESME_RINVPASWD
      logger.warn "Invalid password."
      # 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."
      close_connection
    else
      logger.warn "Unexpected BindTransceiverResponse. Command status: #{pdu.command_status}"
      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}"
      if @delegate.respond_to?(:message_rejected)
        @delegate.message_rejected(self, mt_message_id, pdu)
      end
    else
      logger.info "Got OK SubmitSmResponse (#{pdu.message_id} -> #{mt_message_id})"
      if @delegate.respond_to?(:message_accepted)
        @delegate.message_accepted(self, mt_message_id, pdu)
      end        
    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}"
      if @delegate.respond_to?(:message_rejected)
        @delegate.message_rejected(self, mt_message_id, pdu)
      end
    else
      logger.info "Got OK SubmitMultiResponse (#{pdu.message_id} -> #{mt_message_id})"
      if @delegate.respond_to?(:message_accepted)
        @delegate.message_accepted(self, mt_message_id, pdu)
      end
    end
  when Pdu::BindReceiverResponse
    case pdu.command_status
    when Pdu::Base::ESME_ROK
      logger.debug "Bound OK."
      @state = :bound
      if @delegate.respond_to?(:bound)
        @delegate.bound(self)
      end
    when Pdu::Base::ESME_RINVPASWD
      logger.warn "Invalid password."
      # 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."
      close_connection
    else
      logger.warn "Unexpected BindReceiverResponse. Command status: #{pdu.command_status}"
      close_connection
    end
  else
    logger.warn "(#{self.class.name}) Received unexpected PDU: #{pdu.to_human}."
    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
117
118
# 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")}"
      if @delegate.respond_to?(:data_error)
        @delegate.data_error(e)
      end
    end

  end
end

#send_unbindObject



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

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



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 70

def start_enquire_link_timer(delay_secs)
  logger.info "Starting enquire link timer (with #{delay_secs}s interval)"
  EventMachine::PeriodicTimer.new(delay_secs) do 
    if error?
      logger.warn "Link timer: Connection is in error state. Disconnecting."
      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.



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

def unbind
  if @delegate.respond_to?(:unbound)
    @delegate.unbound(self)
  end
end

#unbound?Boolean

queries the state of the transmitter - is it bound?

Returns:

  • (Boolean)


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

def unbound?
  @state == :unbound
end