pdu_tools 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d1b59db9f41bc1d37660c6af01c3d7fda3cc2800
4
+ data.tar.gz: 64d571e2f9da156852825a43a967171fc107eae7
5
+ SHA512:
6
+ metadata.gz: 4b8ec02cc0b4c1fb1eedc474877812cdce0906d61233f1ed5bae8bd47b9943e82f1bd7c9071b4158f33bfe8d12c97a54c101db40926003e102ff5e6dde2b687b
7
+ data.tar.gz: c337b899ae90c7435a1ff38696861ecdf99fda21db98e8aa7e0c90fbd1133c55dad0777fabb47beef86951c5de0ea854efd6380f3daa8f0080af532532337bd4
data/lib/pdu_tools.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'phone'
2
+ require 'active_support/all'
3
+
4
+ require_relative './pdu_tools/helpers'
5
+ require_relative './pdu_tools/pdu'
6
+ require_relative './pdu_tools/message_part'
7
+ require_relative './pdu_tools/decoder'
8
+ require_relative './pdu_tools/encoder'
9
+
10
+ module PDUTools
11
+ ALPHABETS = {
12
+ a7bit: '00',
13
+ a8bit: '01',
14
+ a16bit: '10'
15
+ }
16
+
17
+ MAX_MESSAGE_LENGTH = 39015
18
+ MAX_GSM_MESSAGE_7BIT_PART_LENGTH = 152
19
+ MAX_GSM_MESSAGE_16BIT_PART_LENGTH = 66
20
+ end
@@ -0,0 +1,197 @@
1
+
2
+ module PDUTools
3
+ class Decoder
4
+ include Helpers
5
+ # http://read.pudn.com/downloads150/sourcecode/embed/646395/Short%20Message%20in%20PDU%20Encoding.pdf
6
+ def initialize pdu_hex, direction=:sc_to_ms
7
+ @pdu_hex = pdu_hex
8
+ @direction = direction
9
+ end
10
+
11
+ def decode
12
+ @sca_length = take(2, :integer) * 2 # Service center address length
13
+ if @sca_length > 0
14
+ @sca_type = parse_address_type take(2) # Service center address type
15
+ @sca = parse_address take(@sca_length - 2), @sca_type # Service center address
16
+ end
17
+ @pdu_type = parse_pdu_type take(2, :binary) # PDU type octet
18
+ @message_reference = take(2) if @pdu_type[:mti] == :sms_submit
19
+ @address_length = take(2, :integer)
20
+ @address_type = parse_address_type take(2)
21
+ @address = parse_address take(@address_length), @address_type
22
+ @pid = take(2)
23
+ @data_coding_scheme = parse_data_coding_scheme take(2, :binary)
24
+ @sc_timestamp = parse_7byte_timestamp take(14) if @pdu_type[:mti] == :sms_deliver
25
+ case @pdu_type[:vpf]
26
+ when :absolute
27
+ @validity_period = parse_7byte_timestamp take(14)
28
+ when :relative
29
+ @validity_period = parse_validity_period take(2, :integer)
30
+ end
31
+ @user_data_length = take(2, :integer)
32
+ parse_user_data @user_data_length
33
+
34
+ MessagePart.new @address, @message, @sc_timestamp, @validity_period, @user_data_header
35
+ end
36
+
37
+ def inspect2
38
+ r = "<PDUTools::Decoder"
39
+ r << "PDU: #{@pdu_hex}\n"
40
+ r << "SCA LENGTH: #{@sca_length}\n"
41
+ r << "SCA TYPE: #{@sca_type}\n"
42
+ r << "SCA: #{@sca}\n"
43
+ r << "PDU TYPE: #{@pdu_type}\n"
44
+ r << "MESSAGE REFERENCE: #{@message_reference}\n" if @message_reference
45
+ r << ">"
46
+ r
47
+ end
48
+
49
+ private
50
+
51
+ def take n, format=:string, data=@pdu_hex
52
+ part = data.slice!(0,n)
53
+ case format
54
+ when :string
55
+ return part
56
+ when :integer
57
+ return part.to_i(16)
58
+ when :binary
59
+ bytes = n/2
60
+ return "%0#{bytes*8}b" % part.to_i(16)
61
+ end
62
+ end
63
+
64
+ def parse_address_type type
65
+ case type
66
+ when "91"
67
+ :international
68
+ when "81"
69
+ :national
70
+ else
71
+ raise StandardError, "unknown address type"
72
+ end
73
+ end
74
+
75
+ def parse_address address, type
76
+ address = swapped2normal address
77
+ if type == :international
78
+ address.prepend "+"
79
+ end
80
+ address
81
+ end
82
+
83
+ def parse_pdu_type pdu_type
84
+ rp = pdu_type.slice!(0,1) == "1"
85
+ udhi = pdu_type.slice!(0,1) == "1"
86
+ srr_or_sri = pdu_type.slice!(0,1) == "1"
87
+ vpf = (case pdu_type.slice!(0,2)
88
+ when "00", "01"
89
+ :none
90
+ when "10"
91
+ :relative
92
+ when "11"
93
+ :absolute
94
+ end)
95
+ rd_or_mms = pdu_type.slice!(0,1)
96
+ mti = pdu_type.slice!(0,2)
97
+
98
+ type = { rp: rp, udhi: udhi, vpf: vpf }
99
+ case @direction
100
+ when :sc_to_ms
101
+ type[:mti] = case mti
102
+ when "00"
103
+ :sms_deliver
104
+ when "01"
105
+ :sms_submit_report
106
+ when "10"
107
+ :sms_status_report
108
+ when "11"
109
+ :reserved
110
+ end
111
+ type[:sri] = srr_or_sri
112
+ type[:mms] = rd_or_mms == "0"
113
+ when :ms_to_sc
114
+ type[:mti] = case mti
115
+ when "00"
116
+ :sms_deliver_report
117
+ when "01"
118
+ :sms_submit
119
+ when "10"
120
+ :sms_command
121
+ when "11"
122
+ :reserved
123
+ end
124
+ type[:srr] = srr_or_sri
125
+ type[:rd] = rd_or_mms == "0"
126
+ end
127
+ type
128
+ end
129
+
130
+ def parse_data_coding_scheme scheme
131
+ {
132
+ coding_group: scheme.slice!(0,2),
133
+ compresion: scheme.slice!(0,1) == "1",
134
+ klass_meaning: scheme.slice!(0,1) == "1",
135
+ alphabet: ALPHABETS.key(scheme.slice!(0,2)),
136
+ klass: scheme.slice!(0,2)
137
+ }
138
+ end
139
+
140
+ def parse_7byte_timestamp timestamp
141
+ year, month, day, hour, minute, second, zone = swapped2normal(timestamp).split('').in_groups_of(2).collect(&:join)
142
+ d = "#{year}-#{month}-#{day} #{hour}:#{minute}:#{second} +%02d:00" % (zone.to_i / 4)
143
+ Time.parse(d)
144
+ end
145
+
146
+ def parse_validity_period period
147
+ case period
148
+ when 0..143
149
+ ((period + 1) * 5).minutes
150
+ when 144..167
151
+ 12.hours + ((period - 143) * 30).minutes
152
+ when 168..196
153
+ (period - 166).days
154
+ when 197..255
155
+ (period - 192).weeks
156
+ end
157
+ end
158
+
159
+ def parse_user_data data_length
160
+ if @pdu_type[:udhi]
161
+ @udh_length = take(2, :integer) * 2
162
+ udh = take(@udh_length)
163
+ @user_data_header = parse_user_data_header udh
164
+ end
165
+ case @data_coding_scheme[:alphabet]
166
+ when :a7bit
167
+ @message = decode7bit @pdu_hex, data_length
168
+ when :a8bit
169
+ @message = decode8bit @pdu_hex, data_length
170
+ when :a16bit
171
+ @message = decode16bit @pdu_hex, data_length
172
+ end
173
+ end
174
+
175
+ def parse_user_data_header header
176
+ iei = take 2, :string, header
177
+ header_length = take 2, :integer, header
178
+ case iei
179
+ when "00"
180
+ reference = take 2, :integer, header
181
+ when "08"
182
+ reference = take 4, :integer, header
183
+ else
184
+ binding.pry
185
+ raise StandardError, "unsupported Information Element Identifier in User Data Header: #{iei}"
186
+ end
187
+ parts = take 2, :integer, header
188
+ part_number = take 2, :integer, header
189
+ {
190
+ reference: reference,
191
+ parts: parts,
192
+ part_number: part_number
193
+ }
194
+ end
195
+
196
+ end
197
+ end
@@ -0,0 +1,161 @@
1
+ module PDUTools
2
+ class Encoder
3
+ include Helpers
4
+
5
+ DEFAULT_OPTIONS = {
6
+ klass: nil,
7
+ require_receipt: false,
8
+ expiry_seconds: nil
9
+ }
10
+
11
+ MessagePart = Struct.new(:data, :length)
12
+
13
+ # PDU structure - http://read.pudn.com/downloads150/sourcecode/embed/646395/Short%20Message%20in%20PDU%20Encoding.pdf
14
+ # X Bytes - SMSC - Service Center Address
15
+ # 1 Byte - Flags / PDU Type
16
+ # - 1 bit Reply Path parameter indicator
17
+ # - 1 bit User Data Header Indicator
18
+ # - 1 bit Status Request Report
19
+ # - 2 bits Validity Period Format
20
+ # - 1 bit Reject Duplicates
21
+ # - 2 bits Message Type Indicator
22
+ # 2 Bytes - Message Reference
23
+ # X Bytes - Address length and address
24
+ # 1 Byte - Protocol identificator (PID)
25
+ # 1 Byte - Data Coding Scheme
26
+ # X Bytes - Validity Period
27
+ # 1 Byte - User Data Length
28
+ # X Bytes - User Data
29
+
30
+ def initialize options
31
+ raise ArgumentError, :recipient unless options[:recipient]
32
+ raise ArgumentError, :message unless options[:message]
33
+ @options = DEFAULT_OPTIONS.merge options
34
+
35
+ @smsc = '00' # Phone Specified
36
+ @message_parts, @alphabet = prepare_message options[:message]
37
+ @pdu_type = pdu_type @concatenated_message_reference, options[:require_receipt], options[:expiry_seconds]
38
+ @message_reference = '00' # Phone Specified
39
+ @address = prepare_recipient options[:recipient]
40
+ @protocol_identifier = '00' # SMS
41
+ @data_coding_scheme = data_coding_scheme options[:klass], @alphabet
42
+ @validity_period = validity_period options[:expiry_seconds]
43
+ end
44
+
45
+ def encode
46
+ head = ""
47
+ head << @smsc
48
+ head << @pdu_type
49
+ head << @message_reference
50
+ head << @address
51
+ head << @protocol_identifier
52
+ head << @data_coding_scheme
53
+ head << @validity_period
54
+ pdus = []
55
+ @message_parts.each do |part|
56
+ pdus << PDU.new(head + part.length + part.data)
57
+ end
58
+ pdus
59
+ end
60
+
61
+ private
62
+ def prepare_message message
63
+ if message.ascii_only?
64
+ # parts = message.scan(/.{1,#{MAX_GSM_MESSAGE_7BIT_PART_LENGTH}}/)
65
+ parts = message.split('').in_groups_of(MAX_GSM_MESSAGE_7BIT_PART_LENGTH).collect(&:join)
66
+ message_parts = []
67
+ parts.each_with_index do |part, i|
68
+ part_gsm0338 = utf8_to_gsm0338 part
69
+ part_7bit = encode7bit(part_gsm0338)
70
+ udh = user_data_header parts.size, i+1
71
+ udh_length = (udh.present? ? (udh.length / 2) + 1 : 0)
72
+ part_length = "%02X" % (part_gsm0338.length + udh_length)
73
+ message_parts << MessagePart.new((udh + part_7bit), part_length)
74
+ end
75
+ [message_parts, :a7bit]
76
+ else
77
+ parts = message.split('').in_groups_of(MAX_GSM_MESSAGE_16BIT_PART_LENGTH).collect(&:join)
78
+ message_parts = []
79
+ parts.each_with_index do |part, i|
80
+ part_8bit = encode8bit(part)
81
+ udh = user_data_header parts.size, i+1
82
+ part_length = "%02X" % ((udh + part_8bit).length / 2)
83
+ message_parts << MessagePart.new((udh + part_8bit), part_length)
84
+ end
85
+ [message_parts, :a16bit]
86
+ end
87
+ end
88
+
89
+ # http://en.wikipedia.org/wiki/Concatenated_SMS#Sending_a_concatenated_SMS_using_a_User_Data_Header
90
+ def user_data_header parts_count, part_number
91
+ return '' if parts_count == 1
92
+ @concatenated_message_reference ||= rand((2**16)-1)
93
+ udh = '06' # Length of User Data Header
94
+ udh << '08' # Concatenated short messages, 16-bit reference number
95
+ udh << '04' # Length of the header, excluding the first two fields
96
+ udh << "%04X" % @concatenated_message_reference
97
+ udh << "%02X" % parts_count
98
+ udh << "%02X" % part_number
99
+ udh
100
+ end
101
+
102
+ def prepare_recipient recipient
103
+ Phoner::Phone.default_country_code ||= "421"
104
+
105
+ address_type = "91" # International
106
+ address = Phoner::Phone.parse(recipient).format("%c%a%n")
107
+ address_length = "%02X" % address.length
108
+ address_encoded = normal2swapped address
109
+ address_length + address_type + address_encoded
110
+ end
111
+
112
+ def data_coding_scheme klass, alphabet
113
+ if klass
114
+ klass_meaning = '1'
115
+ klass = ("%02b" % klass)[-2,2]
116
+ else
117
+ klass_meaning = '0'
118
+ klass = '00'
119
+ end
120
+
121
+ scheme_bin = ""
122
+ scheme_bin << '00' # 2 bits - coding_group
123
+ scheme_bin << '0' # 1 bit - compression
124
+ scheme_bin << klass_meaning # 1 bit - klass meaning flag
125
+ scheme_bin << ALPHABETS[alphabet] # 2 bits - alphabet
126
+ scheme_bin << klass # 2 bits - klass
127
+
128
+ data_coding_scheme_dec = scheme_bin.to_i(2)
129
+ dec2hexbyte data_coding_scheme_dec
130
+ end
131
+
132
+ def pdu_type uhdi, srr, vpf
133
+ reply_path = '0'
134
+ uhdi_flag = (uhdi ? '1' : '0') # User Data Header indicator
135
+ srr_flag = (srr ? '1' : '0') # Status Request Report
136
+ vpf_flag = (vpf ? '10' : '00') # Validity Period Format
137
+ rj = '0' # Reject Duplicates
138
+ mti = '01' # Message Type Indicator (SMS-SUBMIT)
139
+ first_octet_dec = (reply_path + uhdi_flag + srr_flag + vpf_flag + rj + mti).to_i(2)
140
+ dec2hexbyte first_octet_dec
141
+ end
142
+
143
+ def validity_period expiry_seconds
144
+ return '' unless expiry_seconds
145
+ raise ArgumentError, "Expiry must be at least 300 seconds (5 minutes)" if expiry_seconds < 5.minutes
146
+ validity_period_dec = case expiry_seconds
147
+ when 5.minutes..12.hours
148
+ (expiry_seconds / 5.minutes) - 1
149
+ when 12.hours..24.hours
150
+ ((expiry_seconds - 12.hours) / 5.minutes) + 143
151
+ when 24.hours..30.days
152
+ (expiry_seconds / 24.hours) + 166
153
+ when 30.days..63.weeks
154
+ (expiry_seconds / 1.week) + 192
155
+ else
156
+ raise ArgumentError, "Expiry must be 38102400 seconds (63 weeks) or less"
157
+ end
158
+ dec2hexbyte validity_period_dec.ceil
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,111 @@
1
+ module PDUTools
2
+ module Helpers
3
+ GSM_03_38_ESCAPES = {
4
+ "@" => "\x00",
5
+ "$" => "\x02",
6
+ "_" => "\x11",
7
+ "^" => "\x1B\x14",
8
+ "{" => "\x1B\x28",
9
+ "}" => "\x1B\x29",
10
+ "\\" => "\x1B\x2F",
11
+ "[" => "\x1B\x3C",
12
+ "~" => "\x1B\x3D",
13
+ "]" => "\x1B\x3E",
14
+ "|" => "\x1B\x40"
15
+ # "\x80" => "\x1B\x65"
16
+ }
17
+
18
+ def utf8_to_gsm0338 string
19
+ GSM_03_38_ESCAPES.each do |find, replace|
20
+ string.gsub! find, replace
21
+ end
22
+ string
23
+ end
24
+
25
+ def dec2hexbyte dec
26
+ "%02X" % dec
27
+ end
28
+
29
+ def encode7bit string, padding=0
30
+ current_byte = 0
31
+ offset = padding
32
+ packed = []
33
+ string.chars.to_a.each_with_index do |char, i|
34
+ # cap off any excess bytes
35
+ septet = char.ord & 0x7F
36
+ # append the septet and then cap off excess bytes
37
+ current_byte |= (septet << offset) & 0xFF
38
+ offset += 7
39
+ if offset > 7
40
+ # the current byte is full, add it to the encoded data.
41
+ packed << current_byte
42
+ # shift left and append the left shifted septet to the current byte
43
+ septet = septet >> (7 - (offset - 8 ))
44
+ current_byte = septet
45
+ # update offset
46
+ offset -= 8
47
+ end
48
+ end
49
+ packed << current_byte if offset > 0 # append the last byte
50
+ packed.collect{|c| "%02X" % c }.join
51
+ end
52
+
53
+ def encode8bit string
54
+ string.chars.to_a.collect do |char|
55
+ "%04X" % char.ord
56
+ end.join
57
+ end
58
+
59
+ def normal2swapped string
60
+ string << "F" if string.length.odd?
61
+ string.scan(/../).collect(&:reverse).join
62
+ end
63
+
64
+ def swapped2normal string
65
+ string.scan(/../).collect(&:reverse).join.gsub(/F$/,'')
66
+ end
67
+
68
+ # def decode7bit data, length
69
+ # septets = data.to_i(16).to_s(2).split('').in_groups_of(7).collect(&:join)[0,length]
70
+ # septets.collect do |s|
71
+ # s.to_i(2).chr
72
+ # end.join
73
+ # end
74
+
75
+ def decode7bit textdata, length
76
+ bytes = []
77
+ textdata.split('').each_slice(2) do |s|
78
+ bytes << "%08b" % s.join.to_i(16)
79
+ end
80
+ bit = (bytes.size % 7)
81
+ bytes.reverse!
82
+ bytes.each_with_index do |byte, index|
83
+ if bit == 0 or index == 0
84
+ bytes.insert(index, "")
85
+ bit = 7 if bit == 0
86
+ next
87
+ else
88
+ bytes[index-1] = "#{bytes[index-1]}#{(bytes[index]||"")[0,bit]}"
89
+ bytes[index] = bytes[index][bit..-1] if bytes[index]
90
+ bit -= 1
91
+ end
92
+ end
93
+ bytes = bytes.reverse.collect{|b| "0#{b}".to_i(2) }.collect{|b| b.zero? ? nil : b }.compact
94
+ bytes.collect{|b| b.chr }.join
95
+ end
96
+
97
+ def decode8bit data, length
98
+ octets = data.split('').in_groups_of(2).collect(&:join)[0, length]
99
+ octets.collect do |o|
100
+ o.to_i(16).chr
101
+ end.join
102
+ end
103
+
104
+ def decode16bit data, length
105
+ dobule_octets = data.split('').in_groups_of(4).collect(&:join)[0, length/2]
106
+ dobule_octets.collect do |o|
107
+ [o.to_i(16)].pack("U")
108
+ end.join
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,21 @@
1
+ module PDUTools
2
+ class MessagePart
3
+ attr_reader :address, :body, :timestamp, :validity_period, :user_data_header
4
+ def initialize address, body, timestamp, validity_period, user_data_header
5
+ @address = address
6
+ @body = body
7
+ @timestamp = timestamp
8
+ @validity_period = validity_period
9
+ @user_data_header = user_data_header
10
+ end
11
+
12
+ def complete?
13
+ return true unless @user_data_header
14
+ if @user_data_header[:parts] > 1
15
+ false
16
+ else
17
+ true
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ module PDUTools
2
+ class PDU
3
+ attr_reader :pdu_hex
4
+ def initialize pdu_hex
5
+ @pdu_hex = pdu_hex
6
+ end
7
+
8
+ def checksum
9
+ @checksum ||= begin
10
+ sum = @pdu_hex.scan(/../).collect{|c| c.to_i(16)}.sum
11
+ "%02X" % (sum & 0xFF)
12
+ end
13
+ end
14
+
15
+ def length
16
+ @length ||= (@pdu_hex.length / 2) - 1
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+
5
+ describe PDUTools::Decoder do
6
+ let(:decoder) { PDUTools::Decoder.new pdu, :ms_to_sc }
7
+
8
+ context "7 bit data" do
9
+ let(:pdu) { "0001000C9124910001100000001654747A0E4ACF416110BD3CA783DAE5F93C7C2E03" }
10
+ it "should decode" do
11
+ message_part = decoder.decode
12
+ expect(message_part.body).to eq "This is a test message"
13
+ expect(message_part.address).to eq "+421900100100"
14
+ end
15
+ end
16
+
17
+ context "16 bit data" do
18
+ let(:pdu) { "0001000C9124910001100000083E0054006800690073002000690073002000640069006100630072006900740069006300730020013E0161010D0165017E00FD00E100ED00E400FA00F40148" }
19
+ it "should decode" do
20
+ message_part = decoder.decode
21
+ expect(message_part.body).to eq "This is diacritics ľščťžýáíäúôň"
22
+ expect(message_part.address).to eq "+421900100100"
23
+ end
24
+ end
25
+
26
+ context "user data header" do
27
+ let(:pdu) { "0041000C912491000110000000A0060804C643020154747A0E4ACF416110BD3CA783DAE5F93C7C2E53D1E939283D078541F4F29C0E6A97E7F3F0B94C45A7E7A0F41C1406D1CB733AA85D9ECFC3E732159D9E83D2735018442FCFE9A076793E0F9FCB54747A0E4ACF416110BD3CA783DAE5F93C7C2E53D1E939283D078541F4F29C0E6A97E7F3F0B94C45A7E7A0F41C1406D1CB733AA85D9ECFC3" }
28
+ it "should decode" do
29
+ message_part = decoder.decode
30
+ expect(message_part.user_data_header).to be_present
31
+ expect(message_part.user_data_header[:parts]).to eq 2
32
+ expect(message_part.user_data_header[:part_number]).to eq 1
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe PDUTools::Encoder do
5
+ let(:recipient) { "+421 900 100 100" }
6
+ let(:encoder) { PDUTools::Encoder.new recipient: recipient, message: message }
7
+ context "short" do
8
+ context "7bit text" do
9
+ let(:message) { "This is a test message" }
10
+ it "should encode pdu" do
11
+ pdus = encoder.encode
12
+ expect(pdus.size).to eq(1)
13
+ end
14
+ end
15
+
16
+ context "16bit text" do
17
+ let(:message) { "This is diacritics ľščťžýáíäúôň" }
18
+ it "should encode pdu" do
19
+ pdus = encoder.encode
20
+ expect(pdus.size).to eq(1)
21
+ end
22
+ end
23
+ end
24
+
25
+ context "lonh" do
26
+ context "7bit text" do
27
+ let(:message) { "This is a test message" * 10 }
28
+ it "should encode pdu" do
29
+ pdus = encoder.encode
30
+ expect(pdus.size).to eq(2)
31
+ end
32
+ end
33
+
34
+ context "16bit text" do
35
+ let(:message) { "This is diacritics ľščťžýáíäúôň" * 3 }
36
+ it "should encode pdu" do
37
+ pdus = encoder.encode
38
+ expect(pdus.size).to eq(2)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1 @@
1
+ require 'pdu_tools'
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdu_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Filip Zachar
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2014-10-06 00:00:00 Z
13
+ dependencies: []
14
+
15
+ description: Tools for encoding and decoding GSM SMS PDUs.
16
+ email: tulak45@gmail.com
17
+ executables: []
18
+
19
+ extensions: []
20
+
21
+ extra_rdoc_files: []
22
+
23
+ files:
24
+ - lib/pdu_tools/decoder.rb
25
+ - lib/pdu_tools/encoder.rb
26
+ - lib/pdu_tools/helpers.rb
27
+ - lib/pdu_tools/message_part.rb
28
+ - lib/pdu_tools/pdu.rb
29
+ - lib/pdu_tools.rb
30
+ - spec/decoder_spec.rb
31
+ - spec/encoder_spec.rb
32
+ - spec/spec_helper.rb
33
+ homepage: http://rubygems.org/gems/pdu_tools
34
+ licenses:
35
+ - MIT
36
+ metadata: {}
37
+
38
+ post_install_message:
39
+ rdoc_options: []
40
+
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - &id001
46
+ - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - *id001
52
+ requirements: []
53
+
54
+ rubyforge_project:
55
+ rubygems_version: 2.0.4
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Tools for encoding and decoding GSM SMS PDUs.
59
+ test_files: []
60
+