From a001604ee76d4ec2e863e3e87a7138f2cc33f3cb Mon Sep 17 00:00:00 2001 From: Guillaume DOTT Date: Fri, 6 Sep 2013 10:17:47 +0200 Subject: [PATCH] Add PDU mode support and GSM 7Bit encoding / decoding --- lib/biju.rb | 1 + lib/biju/pdu.rb | 63 ++++++++++++++++++++++++++++++++ lib/biju/pdu/gsm7bit.rb | 81 +++++++++++++++++++++++++++++++++++++++++ lib/biju/sms.rb | 12 +++++- 4 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 lib/biju/pdu.rb create mode 100644 lib/biju/pdu/gsm7bit.rb diff --git a/lib/biju.rb b/lib/biju.rb index 947bf99..923ff35 100644 --- a/lib/biju.rb +++ b/lib/biju.rb @@ -1,5 +1,6 @@ require 'biju/version' require 'biju/modem' +require 'biju/pdu' require 'biju/to_hayes' require 'biju/hayes' require 'biju/sms' diff --git a/lib/biju/pdu.rb b/lib/biju/pdu.rb new file mode 100644 index 0000000..a99e3da --- /dev/null +++ b/lib/biju/pdu.rb @@ -0,0 +1,63 @@ +require 'biju/pdu/gsm7bit' + +module Biju + module PDU + ENCODING = { + gsm7bit: GSM7Bit, + } + + def self.encode(hash) + end + + def self.encode_user_data(message, encoding = :gsm7bit) + raise ArgumentError, "Unknown encoding" unless ENCODING.has_key?(encoding) + ENCODING[encoding].encode(message) + end + + def self.decode(string) + res = { + smsc_length: string[0..1], + smsc_type: string[2..3], + smsc_number: string[4..15], + + address_length: string[18..19].to_i(16), + address_type: string[20..21], + + protocol_identifier: string[34..35], + data_coding_scheme: string[36..37], + timestamp: DateTime.strptime( + "#{string[38..49].reverse}+#{string[50..51].reverse}", + '%S%M%H%d%m%y%Z'), + user_data_length: string[52..53], + } + res[:sender_number] = string[22..(22 + res[:address_length])] + res[:user_data] = PDU.decode_user_data( + string[54..-1], res[:data_coding_scheme]) + + res + end + + def self.decode_user_data(message, encoding = '00') + encoding = data_coding_scheme(encoding) unless encoding.is_a?(Symbol) + + raise ArgumentError, "Unknown encoding" unless ENCODING.has_key?(:gsm7bit) + ENCODING[:gsm7bit].decode(message) + end + + def self.data_coding_scheme(dcs) + dcs = dcs.hex if dcs.is_a?(String) + if dcs & 0b11000000 == 0 + case dcs & 0b00001100 + when 0 + :gsm7bit + when 4 + :gsm8bit + when 8 + :ucs2 + when 12 + :reserved + end + end + end + end +end diff --git a/lib/biju/pdu/gsm7bit.rb b/lib/biju/pdu/gsm7bit.rb new file mode 100644 index 0000000..b6728c6 --- /dev/null +++ b/lib/biju/pdu/gsm7bit.rb @@ -0,0 +1,81 @@ +module Biju::PDU + class GSM7Bit + BASIC_7BIT_CHARACTER_SET = [ + '@', '£', '$', '¥', 'è', 'é', 'ù', 'ì', 'ò', 'Ç', "\n", 'Ø', 'ø', "\r", 'Å', 'å', + "\u0394", '_', "\u03a6", "\u0393", "\u039b", "\u03a9", "\u03a0","\u03a8", "\u03a3", "\u0398", "\u039e", "\e", 'Æ', 'æ', 'ß', 'É', + ' ', '!', '"', '#', '¤', '%', '&', '\'', '(', ')','*', '+', ',', '-', '.', '/', + '0', '1', '2', '3', '4', '5', '6', '7','8', '9', ':', ';', '<', '=', '>', '?', + '¡', 'A', 'B', 'C', 'D', 'E','F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S','T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'Ä', 'Ö', 'Ñ', 'Ü', '§', + '¿', 'a','b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'ä', 'ö', 'ñ','ü', 'à' + ] + + BASIC_7BIT_CHARACTER_SET_EXTENSION = { + 0x0A => "\n", + 0x0D => '', + 0x14 => '^', + 0x1B => '', + 0x28 => '{', + 0x29 => '}', + 0x2F => '\\', + 0x3C => '[', + 0x3D => '~', + 0x3E => ']', + 0x40 => '|', + 0x65 => '€', + } + + def self.decode(string) + res = '' + next_char = 0 + + string.scan(/../).map(&:hex).each_with_index do |octet, i| + index = i % 7 + current = ((octet & (2 ** (7 - index) - 1)) << index) | next_char + + res = add_char(res, current) + + next_char = octet >> (7 - index) + if index == 6 + res = add_char(res, next_char) + next_char = 0 + end + end + + res + end + + def self.encode(string) + res = '' + string.chars.each do |char| + if get_septet(char) + res << get_septet(char).reverse + elsif get_septet(char, escape: true) + res << get_septet("\e").reverse + res << get_septet(char, escape: true).reverse + end + end + res << ("0" * (8 - (res.length % 8))) unless res.length % 8 == 0 + + res.scan(/.{8}/).map { |octet| "%02x" % octet.reverse.to_i(2) }.join + end + + private + + def self.add_char(string, char) + if string[-1] == "\e" + string.chop << BASIC_7BIT_CHARACTER_SET_EXTENSION[char] + else + string << BASIC_7BIT_CHARACTER_SET[char] + end + end + + def self.get_septet(char, escape: false) + char = (!escape ? BASIC_7BIT_CHARACTER_SET.index(char) : BASIC_7BIT_CHARACTER_SET_EXTENSION.key(char)) + + return nil unless char + "%07b" % char + end + end +end diff --git a/lib/biju/sms.rb b/lib/biju/sms.rb index d576aed..d6b242c 100644 --- a/lib/biju/sms.rb +++ b/lib/biju/sms.rb @@ -5,6 +5,13 @@ module Biju attr_accessor :id, :phone_number, :message attr_reader :datetime + def self.from_pdu(string) + sms_infos = PDU.decode(string) + new(phone_number: sms_infos[:sender_number], + datetime: sms_infos[:timestamp], + message: sms_infos[:user_data]) + end + def initialize(params={}) params.each do |attr, value| self.public_send("#{attr}=", value) @@ -23,7 +30,10 @@ module Biju end def to_s - "#{id} - #{phone_number} - #{datetime} - #{message}" + "[#{id}] (#{phone_number}) #{datetime} '#{message}'" + end + + def to_pdu end end end