class PDUTools::Encoder

Constants

DEFAULT_OPTIONS
MessagePart

Public Class Methods

new(options) click to toggle source

PDU structure - read.pudn.com/downloads150/sourcecode/embed/646395/Short%20Message%20in%20PDU%20Encoding.pdf X Bytes - SMSC - Service Center Address 1 Byte - Flags / PDU Type

- 1 bit  Reply Path parameter indicator
- 1 bit  User Data Header Indicator
- 1 bit  Status Request Report
- 2 bits Validity Period Format
- 1 bit  Reject Duplicates
- 2 bits Message Type Indicator

2 Bytes - Message Reference X Bytes - Address length and address 1 Byte - Protocol identificator (PID) 1 Byte - Data Coding Scheme X Bytes - Validity Period 1 Byte - User Data Length X Bytes - User Data

# File lib/pdu_tools/encoder.rb, line 30
def initialize options
  raise ArgumentError, :recipient unless options[:recipient]
  raise ArgumentError, :message unless options[:message]
  @options = DEFAULT_OPTIONS.merge options

  @smsc = '00' # Phone Specified
  @message_parts, @alphabet = prepare_message options[:message]
  @pdu_type = pdu_type @concatenated_message_reference, options[:require_receipt], options[:expiry_seconds]
  @message_reference = '00' # Phone Specified
  @address = prepare_recipient options[:recipient]
  @protocol_identifier = '00' # SMS
  @data_coding_scheme = data_coding_scheme options[:klass], @alphabet
  @validity_period = validity_period options[:expiry_seconds]
end

Public Instance Methods

encode() click to toggle source
# File lib/pdu_tools/encoder.rb, line 45
def encode
  head = ""
  head << @smsc
  head << @pdu_type
  head << @message_reference
  head << @address
  head << @protocol_identifier
  head << @data_coding_scheme
  head << @validity_period
  pdus = []
  @message_parts.each do |part|
    pdus << PDU.new(head + part.length + part.data)
  end
  pdus
end

Private Instance Methods

data_coding_scheme(klass, alphabet) click to toggle source
# File lib/pdu_tools/encoder.rb, line 115
def data_coding_scheme klass, alphabet
  if klass
    klass_meaning = '1'
    klass = ("%02b" % klass)[-2,2]
  else
    klass_meaning = '0'
    klass = '00'
  end

  scheme_bin = ""
  scheme_bin << '00'                # 2 bits - coding_group
  scheme_bin << '0'                 # 1 bit  - compression
  scheme_bin << klass_meaning       # 1 bit  - klass meaning flag
  scheme_bin << ALPHABETS[alphabet] # 2 bits - alphabet
  scheme_bin << klass               # 2 bits - klass

  data_coding_scheme_dec = scheme_bin.to_i(2)
  dec2hexbyte data_coding_scheme_dec
end
pdu_type(uhdi, srr, vpf) click to toggle source
# File lib/pdu_tools/encoder.rb, line 135
def pdu_type uhdi, srr, vpf
  reply_path = '0'
  uhdi_flag = (uhdi ? '1' : '0') # User Data Header indicator
  srr_flag = (srr ? '1' : '0')   # Status Request Report
  vpf_flag = (vpf ? '10' : '00') # Validity Period Format
  rj = '0'                       # Reject Duplicates
  mti = '01'                     # Message Type Indicator (SMS-SUBMIT)
  first_octet_dec = (reply_path + uhdi_flag + srr_flag + vpf_flag + rj + mti).to_i(2)
  dec2hexbyte first_octet_dec
end
prepare_message(message) click to toggle source
# File lib/pdu_tools/encoder.rb, line 62
def prepare_message message
  if message.ascii_only?
    # parts = message.scan(/.{1,#{MAX_GSM_MESSAGE_7BIT_PART_LENGTH}}/)
    parts = message.split('').in_groups_of(MAX_GSM_MESSAGE_7BIT_PART_LENGTH).collect(&:join)
    message_parts = []
    parts.each_with_index do |part, i|
      part_gsm0338 = utf8_to_gsm0338 part
      part_7bit = encode7bit(part_gsm0338)
      udh = user_data_header parts.size, i+1
      udh_length = (udh.present? ? (udh.length / 2) + 1 : 0)
      part_length = "%02X" % (part_gsm0338.length + udh_length)
      message_parts << MessagePart.new((udh + part_7bit), part_length)
    end
    [message_parts, :a7bit]
  else
    parts = message.split('').in_groups_of(MAX_GSM_MESSAGE_16BIT_PART_LENGTH).collect(&:join)
    message_parts = []
    parts.each_with_index do |part, i|
      part_8bit = encode8bit(part)
      udh = user_data_header parts.size, i+1
      part_length = "%02X" % ((udh + part_8bit).length / 2)
      message_parts << MessagePart.new((udh + part_8bit), part_length)
    end
    [message_parts, :a16bit]
  end
end
prepare_recipient(recipient) click to toggle source
# File lib/pdu_tools/encoder.rb, line 102
def prepare_recipient recipient
  Phoner::Phone.default_country_code ||= "421"
  address_type = "91" # International
  if @options[:force_recipient]
    address = recipient
  else
    address = Phoner::Phone.parse(recipient).format("%c%a%n")
  end
  address_length = "%02X" % address.length
  address_encoded = normal2swapped address
  address_length + address_type + address_encoded
end
user_data_header(parts_count, part_number) click to toggle source

en.wikipedia.org/wiki/Concatenated_SMS#Sending_a_concatenated_SMS_using_a_User_Data_Header

# File lib/pdu_tools/encoder.rb, line 90
def user_data_header parts_count, part_number
  return '' if parts_count == 1
  @concatenated_message_reference ||= rand((2**16)-1)
  udh =  '06' # Length of User Data Header
  udh << '08' # Concatenated short messages, 16-bit reference number
  udh << '04' # Length of the header, excluding the first two fields
  udh << "%04X" % @concatenated_message_reference
  udh << "%02X" % parts_count
  udh << "%02X" % part_number
  udh
end
validity_period(expiry_seconds) click to toggle source
# File lib/pdu_tools/encoder.rb, line 146
def validity_period expiry_seconds
  return '' unless expiry_seconds
  raise ArgumentError, "Expiry must be at least 300 seconds (5 minutes)" if expiry_seconds < 5.minutes
  validity_period_dec = case expiry_seconds
                        when 5.minutes..12.hours
                          (expiry_seconds / 5.minutes) - 1
                        when 12.hours..24.hours
                          ((expiry_seconds - 12.hours) / 5.minutes) + 143
                        when 24.hours..30.days
                          (expiry_seconds / 24.hours) + 166
                        when 30.days..63.weeks
                          (expiry_seconds / 1.week) + 192
                        else
                          raise ArgumentError, "Expiry must be 38102400 seconds (63 weeks) or less"
                        end
  dec2hexbyte validity_period_dec.ceil
end