class Smpp::Base

Attributes

state[RW]

:bound or :unbound

Public Class Methods

logger() click to toggle source
# File lib/smpp/base.rb, line 39
def Base.logger
  @@logger
end
logger=(logger) click to toggle source
# File lib/smpp/base.rb, line 43
def Base.logger=(logger)
  @@logger = logger
end
new(config, delegate) click to toggle source
# 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

Private Class Methods

hex_debug(data, prefix = "") click to toggle source
# File lib/smpp/base.rb, line 293
def Base.hex_debug(data, prefix = "")
  logger.debug do
    message = "Hex dump follows:\n"
    hexdump(data).each_line do |line|
      message << (prefix + line.chomp + "\n")
    end
    message
  end
end
hexdump(target) click to toggle source
# File lib/smpp/base.rb, line 303
def Base.hexdump(target)
  width=16
  group=2

  output = ""
  n=0
  ascii=''
  target.each_byte { |b|
    if n%width == 0
      output << "%s\n%08x: "%[ascii,n]
      ascii='| '
    end
    output << "%02x"%b
    output << ' ' if (n+=1)%group==0
    ascii << "%s"%b.chr.tr('^ -~','.')
  }
  output << ' '*(((2+width-ascii.size)*(2*group+1))/group.to_f).ceil+ascii
  output[1..-1]
end

Public Instance Methods

bound?() click to toggle source
# File lib/smpp/base.rb, line 35
def bound?
  @state == :bound
end
logger() click to toggle source
# File lib/smpp/base.rb, line 47
def logger
  @@logger
end
post_init() click to toggle source

invoked by EventMachine when connected

# 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) click to toggle source

process common PDUs returns true if no further processing necessary

# 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) click to toggle source

EventMachine::Connection#receive_data

# 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) click to toggle source
# File lib/smpp/base.rb, line 130
def run_callback(cb, *args)
  if @delegate.respond_to?(cb)
    @delegate.send(cb, *args)
  end
end
send_unbind() click to toggle source
# File lib/smpp/base.rb, line 125
def send_unbind
  write_pdu Pdu::Unbind.new
  @state = :unbound
end
unbind() click to toggle source

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

# File lib/smpp/base.rb, line 121
def unbind
  run_callback(:unbound, self)
end
unbound?() click to toggle source

queries the state of the transmitter - is it bound?

# File lib/smpp/base.rb, line 31
def unbound?
  @state == :unbound
end

Private Instance Methods

hex_debug(data, prefix = "") click to toggle source
# File lib/smpp/base.rb, line 289
def hex_debug(data, prefix = "")
  Base.hex_debug(data, prefix)
end
read_pdu(data) click to toggle source
# File lib/smpp/base.rb, line 271
def read_pdu(data)
  pdu = nil
  # we may either receive a new request or a response to a previous response.
  begin
    pdu = Pdu::Base.create(data)
    if !pdu
      logger.warn "Not able to parse PDU!"
    else
      logger.debug "-> " + pdu.to_human
    end
    hex_debug data, "-> "
  rescue Exception => ex
    logger.error "Exception while reading PDUs: #{ex} in #{ex.backtrace[0]}"
    raise
  end
  pdu
end
write_pdu(pdu) click to toggle source
# File lib/smpp/base.rb, line 265
def write_pdu(pdu)
  logger.debug "<- #{pdu.to_human}"
  hex_debug pdu.data, "<- "
  send_data pdu.data
end