diff --git a/lib/biju/pdu.rb b/lib/biju/pdu.rb index baba27b..5f86e77 100644 --- a/lib/biju/pdu.rb +++ b/lib/biju/pdu.rb @@ -1,5 +1,5 @@ -require 'biju/pdu/gsm7bit' -require 'biju/pdu/ucs2' +require 'biju/pdu/encoding/gsm7bit' +require 'biju/pdu/encoding/ucs2' require 'biju/pdu/user_data' require 'biju/pdu/data_coding_scheme' diff --git a/lib/biju/pdu/data_coding_scheme.rb b/lib/biju/pdu/data_coding_scheme.rb index 541767b..f96a82b 100644 --- a/lib/biju/pdu/data_coding_scheme.rb +++ b/lib/biju/pdu/data_coding_scheme.rb @@ -10,8 +10,8 @@ module Biju def self.autodetect(message) message.chars.each do |char| - return :ucs2 unless GSM7Bit::BASIC_7BIT_CHARACTER_SET.include?(char) || - GSM7Bit::BASIC_7BIT_CHARACTER_SET_EXTENSION.has_value?(char) + return :ucs2 unless Encoding::GSM7Bit::BASIC_7BIT_CHARACTER_SET.include?(char) || + Encoding::GSM7Bit::BASIC_7BIT_CHARACTER_SET_EXTENSION.has_value?(char) end :gsm7bit @@ -23,7 +23,7 @@ module Biju if dcs & 0b11000000 == 0 dcs = DATA_CODING_SCHEME.key(dcs & 0b00001100) else - raise "Unsupported" + raise 'Unsupported' end end diff --git a/lib/biju/pdu/encoding/gsm7bit.rb b/lib/biju/pdu/encoding/gsm7bit.rb new file mode 100644 index 0000000..854f950 --- /dev/null +++ b/lib/biju/pdu/encoding/gsm7bit.rb @@ -0,0 +1,97 @@ +module Biju + module PDU + module Encoding + 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, length: 0) + res = '' + next_char = 0 + current_length = 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) + current_length += 1 + + break if length > 0 && current_length >= length + + next_char = octet >> (7 - index) + if index == 6 + res = add_char(res, next_char) + current_length += 1 + next_char = 0 + end + end + + res + end + + def self.encode(string) + res = '' + length = 0 + + string.chars.each do |char| + if get_septet(char) + res << get_septet(char).reverse + length += 1 + elsif get_septet(char, escape: true) + res << get_septet("\e").reverse + res << get_septet(char, escape: true).reverse + length += 2 + 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, + length: length, + ] + 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 + end +end diff --git a/lib/biju/pdu/encoding/ucs2.rb b/lib/biju/pdu/encoding/ucs2.rb new file mode 100644 index 0000000..f557965 --- /dev/null +++ b/lib/biju/pdu/encoding/ucs2.rb @@ -0,0 +1,19 @@ +module Biju + module PDU + module Encoding + class UCS2 + def self.decode(string, length: 0) + string.scan(/.{4}/).map { |char| char.hex.chr('UCS-2BE') }.join + .encode('UTF-8', 'UCS-2BE') + end + + def self.encode(string) + [ + string.encode('UCS-2BE').chars.map { |char| "%04x" % char.ord }.join, + length: string.length * 2, + ] + end + end + end + end +end diff --git a/lib/biju/pdu/gsm7bit.rb b/lib/biju/pdu/gsm7bit.rb deleted file mode 100644 index bd325bf..0000000 --- a/lib/biju/pdu/gsm7bit.rb +++ /dev/null @@ -1,95 +0,0 @@ -module Biju - module 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, length: 0) - res = '' - next_char = 0 - current_length = 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) - current_length += 1 - - break if length > 0 && current_length >= length - - next_char = octet >> (7 - index) - if index == 6 - res = add_char(res, next_char) - current_length += 1 - next_char = 0 - end - end - - res - end - - def self.encode(string) - res = '' - length = 0 - - string.chars.each do |char| - if get_septet(char) - res << get_septet(char).reverse - length += 1 - elsif get_septet(char, escape: true) - res << get_septet("\e").reverse - res << get_septet(char, escape: true).reverse - length += 2 - 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, - length: length, - ] - 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 -end diff --git a/lib/biju/pdu/ucs2.rb b/lib/biju/pdu/ucs2.rb deleted file mode 100644 index e8ff757..0000000 --- a/lib/biju/pdu/ucs2.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Biju - module PDU - class UCS2 - def self.decode(string, length: 0) - string.scan(/.{4}/).map { |char| char.hex.chr('UCS-2BE') }.join - .encode('UTF-8', 'UCS-2BE') - end - - def self.encode(string) - [ - string.encode('UCS-2BE').chars.map { |char| "%04x" % char.ord }.join, - length: string.length * 2, - ] - end - end - end -end diff --git a/lib/biju/pdu/user_data.rb b/lib/biju/pdu/user_data.rb index 6f57813..0614eb2 100644 --- a/lib/biju/pdu/user_data.rb +++ b/lib/biju/pdu/user_data.rb @@ -2,8 +2,8 @@ module Biju module PDU class UserData ENCODING = { - gsm7bit: GSM7Bit, - ucs2: UCS2, + gsm7bit: Encoding::GSM7Bit, + ucs2: Encoding::UCS2, } attr_accessor :encoding, :message, :length diff --git a/spec/biju/pdu/encoding/gsm7bit_spec.rb b/spec/biju/pdu/encoding/gsm7bit_spec.rb new file mode 100644 index 0000000..f2ffed6 --- /dev/null +++ b/spec/biju/pdu/encoding/gsm7bit_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' +require 'biju/pdu/encoding/gsm7bit' + +describe Biju::PDU::Encoding::GSM7Bit do + describe '::decode' do + it "decodes string" do + expect(Biju::PDU::Encoding::GSM7Bit.decode('D4F29C0E', length: 4)).to eq('Test') + end + + it "decodes character from extension set" do + expect(Biju::PDU::Encoding::GSM7Bit.decode('9B32', length: 2)).to eq('€') + end + + it "decodes character with a length of 7" do + expect(Biju::PDU::Encoding::GSM7Bit.decode('E170381C0E8701', length: 7)).to eq('a' * 7) + end + end + + describe '::encode' do + it "encodes string" do + expect(Biju::PDU::Encoding::GSM7Bit.encode('Test').first.upcase).to eq('D4F29C0E') + end + + it "encodes character from extension set" do + expect(Biju::PDU::Encoding::GSM7Bit.encode('€').first.upcase).to eq('9B32') + end + + it "encodes character with a length of 7" do + expect(Biju::PDU::Encoding::GSM7Bit.encode('a' * 7).first.upcase).to eq('E170381C0E8701') + end + end + + it "gives same text after encoding and decoding" do + strings = [ + 'My first TEST', + '{More complicated]', + 'And on€ More~', + 'a' * 7, + ] + + strings.each do |string| + expect(Biju::PDU::Encoding::GSM7Bit.decode( + *Biju::PDU::Encoding::GSM7Bit.encode(string))).to eq(string) + end + end +end diff --git a/spec/biju/pdu/encoding/ucs2_spec.rb b/spec/biju/pdu/encoding/ucs2_spec.rb new file mode 100644 index 0000000..dfe50c2 --- /dev/null +++ b/spec/biju/pdu/encoding/ucs2_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require 'biju/pdu/encoding/ucs2' + +describe Biju::PDU::Encoding::UCS2 do + describe '::decode' do + it "decodes string" do + expect(Biju::PDU::Encoding::UCS2.decode('00C700E700E200E300E500E4016B00F80153', length: 4)).to eq('Ççâãåäūøœ') + end + end + + describe '::encode' do + it "encodes string" do + expect(Biju::PDU::Encoding::UCS2.encode('Ççâãåäūøœ').first.upcase).to eq('00C700E700E200E300E500E4016B00F80153') + end + end + + it "gives same text after encoding and decoding" do + strings = [ + 'My first TEST', + '{More çomplicated]', + 'And on€ More~', + 'þß®', + ] + + strings.each do |string| + expect(Biju::PDU::Encoding::UCS2.decode( + *Biju::PDU::Encoding::UCS2.encode(string))).to eq(string) + end + end +end diff --git a/spec/biju/pdu/gsm7bit_spec.rb b/spec/biju/pdu/gsm7bit_spec.rb deleted file mode 100644 index b888f58..0000000 --- a/spec/biju/pdu/gsm7bit_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' -require 'biju/pdu/gsm7bit' - -describe Biju::PDU::GSM7Bit do - describe '::decode' do - it "decodes string" do - expect(Biju::PDU::GSM7Bit.decode('D4F29C0E', length: 4)).to eq('Test') - end - - it "decodes character from extension set" do - expect(Biju::PDU::GSM7Bit.decode('9B32', length: 2)).to eq('€') - end - - it "decodes character with a length of 7" do - expect(Biju::PDU::GSM7Bit.decode('E170381C0E8701', length: 7)).to eq('a' * 7) - end - end - - describe '::encode' do - it "encodes string" do - expect(Biju::PDU::GSM7Bit.encode('Test').first.upcase).to eq('D4F29C0E') - end - - it "encodes character from extension set" do - expect(Biju::PDU::GSM7Bit.encode('€').first.upcase).to eq('9B32') - end - - it "encodes character with a length of 7" do - expect(Biju::PDU::GSM7Bit.encode('a' * 7).first.upcase).to eq('E170381C0E8701') - end - end - - it "gives same text after encoding and decoding" do - strings = [ - 'My first TEST', - '{More complicated]', - 'And on€ More~', - 'a' * 7, - ] - - strings.each do |string| - expect(Biju::PDU::GSM7Bit.decode( - *Biju::PDU::GSM7Bit.encode(string))).to eq(string) - end - end -end diff --git a/spec/biju/pdu/ucs2_spec.rb b/spec/biju/pdu/ucs2_spec.rb deleted file mode 100644 index 45325a5..0000000 --- a/spec/biju/pdu/ucs2_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' -require 'biju/pdu/ucs2' - -describe Biju::PDU::UCS2 do - describe '::decode' do - it "decodes string" do - expect(Biju::PDU::UCS2.decode('00C700E700E200E300E500E4016B00F80153', length: 4)).to eq('Ççâãåäūøœ') - end - end - - describe '::encode' do - it "encodes string" do - expect(Biju::PDU::UCS2.encode('Ççâãåäūøœ').first.upcase).to eq('00C700E700E200E300E500E4016B00F80153') - end - end - - it "gives same text after encoding and decoding" do - strings = [ - 'My first TEST', - '{More çomplicated]', - 'And on€ More~', - 'þß®', - ] - - strings.each do |string| - expect(Biju::PDU::UCS2.decode( - *Biju::PDU::UCS2.encode(string))).to eq(string) - end - end -end