tlv 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +1 -0
- data/CHANGELOG +10 -0
- data/COPYING +728 -0
- data/LICENSE +58 -0
- data/README +38 -0
- data/Rakefile +163 -0
- data/THANKS +0 -0
- data/TODO +2 -0
- data/bin/parse_dgi +50 -0
- data/bin/parse_tlv +51 -0
- data/lib/tlv.rb +11 -0
- data/lib/tlv/b.rb +32 -0
- data/lib/tlv/constructed.rb +116 -0
- data/lib/tlv/dgi.rb +40 -0
- data/lib/tlv/field.rb +24 -0
- data/lib/tlv/parse.rb +87 -0
- data/lib/tlv/parser/dictionaries/asn.rb +63 -0
- data/lib/tlv/parser/dictionaries/dictionaries.rb +2 -0
- data/lib/tlv/parser/dictionaries/emv_tags.rb +130 -0
- data/lib/tlv/parser/parser.rb +167 -0
- data/lib/tlv/raw.rb +31 -0
- data/lib/tlv/tag.rb +51 -0
- data/lib/tlv/tlv.rb +144 -0
- data/lib/tlv/to_bytes.rb +76 -0
- data/test/constructed_test.rb +106 -0
- data/test/dgi_test.rb +122 -0
- data/test/tlv_tag_test.rb +89 -0
- data/test/tlv_test.rb +217 -0
- metadata +108 -0
data/lib/tlv/raw.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
module TLV
|
2
|
+
class TLV
|
3
|
+
class Raw < Field
|
4
|
+
def initialize clazz, desc=nil, name=nil, len=0
|
5
|
+
desc ||= "Value"
|
6
|
+
super
|
7
|
+
end
|
8
|
+
def define_accessor clazz
|
9
|
+
super
|
10
|
+
name = @name
|
11
|
+
clazz.instance_eval{
|
12
|
+
define_method("#{name}="){|val|
|
13
|
+
val ||= ""
|
14
|
+
raise("must be a String #{val}") unless val.is_a?(String)
|
15
|
+
self.instance_variable_set("@#{name}", val)
|
16
|
+
}
|
17
|
+
|
18
|
+
define_method("#{name}") {
|
19
|
+
self.instance_variable_get("@#{name}") || ""
|
20
|
+
}
|
21
|
+
}
|
22
|
+
end
|
23
|
+
def parse tlv, bytes, length
|
24
|
+
val = bytes[0, length]
|
25
|
+
rest = bytes[length, bytes.length]
|
26
|
+
tlv.send("#{name}=", val)
|
27
|
+
rest
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end # module
|
data/lib/tlv/tag.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module TLV
|
2
|
+
class TLV
|
3
|
+
|
4
|
+
class << self
|
5
|
+
CLASS_MASK = 0xC0
|
6
|
+
UNIVERSAL_CLASS = 0x00
|
7
|
+
APPLICATION = 0x40
|
8
|
+
CTX_SPECIFIC = 0x80
|
9
|
+
PRIVATE = 0xC0
|
10
|
+
|
11
|
+
BYTE6 = 0x20
|
12
|
+
|
13
|
+
def universal?
|
14
|
+
((@tag & CLASS_MASK) == UNIVERSAL_CLASS) if @tag
|
15
|
+
end
|
16
|
+
def application?
|
17
|
+
((@tag & CLASS_MASK) == APPLICATION) if @tag
|
18
|
+
end
|
19
|
+
def context_specific?
|
20
|
+
((@tag & CLASS_MASK) == CTX_SPECIFIC) if @tag
|
21
|
+
end
|
22
|
+
def private?
|
23
|
+
((@tag & CLASS_MASK) == PRIVATE) if @tag
|
24
|
+
end
|
25
|
+
def primitive?
|
26
|
+
if @tag
|
27
|
+
((@tag & BYTE6) == 0x00)
|
28
|
+
else
|
29
|
+
true # `tagless` datastructs are by default primitive
|
30
|
+
end
|
31
|
+
end
|
32
|
+
def constructed?
|
33
|
+
((@tag & BYTE6) == BYTE6) if @tag
|
34
|
+
end
|
35
|
+
# check the tag is approriate length
|
36
|
+
def check_tag
|
37
|
+
if (tag & 0x1f) == 0x1f # last 5 bits set, 2 byte tag
|
38
|
+
raise "Tag too short: #{b2s(tag)} should be 2 bytes" unless tag.length > 1
|
39
|
+
if (tag[1]&0x80) == 0x80
|
40
|
+
raise "Tag length incorrect: #{b2s(tag)} should be 3 bytes" unless tag.length == 3
|
41
|
+
else
|
42
|
+
raise "Tag too long: #{b2s(tag)} should be 2 bytes" if tag.length > 2
|
43
|
+
end
|
44
|
+
else
|
45
|
+
raise "Tag too long: #{b2s(tag)} should be 1 bytes" if tag.length > 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end # module
|
data/lib/tlv/tlv.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
|
2
|
+
module TLV
|
3
|
+
|
4
|
+
def self.b2s bytestr
|
5
|
+
return "" unless bytestr
|
6
|
+
r = bytestr.unpack("H*")[0]
|
7
|
+
r.length > 1 ? r : " "
|
8
|
+
end
|
9
|
+
def self.s2b string
|
10
|
+
return "" unless string
|
11
|
+
string = string.gsub(/\s+/, "")
|
12
|
+
string = "0" + string unless (string.length % 2 == 0)
|
13
|
+
[string].pack("H*")
|
14
|
+
end
|
15
|
+
|
16
|
+
class TLV
|
17
|
+
|
18
|
+
def self.s2b str
|
19
|
+
::TLV.s2b str
|
20
|
+
end
|
21
|
+
def self.b2s bytes
|
22
|
+
::TLV.b2s bytes
|
23
|
+
end
|
24
|
+
# def self.register tag, clazz
|
25
|
+
# @tlv_classes ||= {}
|
26
|
+
# @tlv_classes[tag] = clazz
|
27
|
+
# end
|
28
|
+
|
29
|
+
|
30
|
+
DEBUG = ENV["DEBUG"]
|
31
|
+
|
32
|
+
# Outputs a warning in case the enironment
|
33
|
+
# variable `DEBUG` is set.
|
34
|
+
def self.warn mes
|
35
|
+
STDERR.puts "[warn] #{mes}" if ENV["DEBUG"]
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
# class A < Field
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class AN < Field
|
44
|
+
# end
|
45
|
+
#
|
46
|
+
# class ANS < Field
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
#
|
50
|
+
# class CN < Field
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# class N < Field
|
54
|
+
# end
|
55
|
+
|
56
|
+
class << self
|
57
|
+
attr_accessor :tag
|
58
|
+
attr_accessor :display_name
|
59
|
+
# If this TLV is placed into another as a subfield, this will be
|
60
|
+
# the name of the accessor, default is the rubyfied display_name
|
61
|
+
attr_accessor :accessor_name
|
62
|
+
def tlv tag, display_name, accessor_name=nil
|
63
|
+
@tag = case tag
|
64
|
+
when String
|
65
|
+
TLV.s2b(tag)
|
66
|
+
when Fixnum
|
67
|
+
TLV::s2b("%x" % tag)
|
68
|
+
end
|
69
|
+
def @tag.& flag
|
70
|
+
self[0] & flag
|
71
|
+
end
|
72
|
+
check_tag
|
73
|
+
@display_name = display_name
|
74
|
+
@accessor_name = accessor_name || rubify_a(display_name)
|
75
|
+
TLV.register self
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
|
80
|
+
def fields
|
81
|
+
@fields ||= (self == TLV ? [] : superclass.fields.dup)
|
82
|
+
end
|
83
|
+
|
84
|
+
def b len, desc, name=nil
|
85
|
+
raise "invalid len #{len}" unless (len%8 == 0)
|
86
|
+
fields << B.new(self, desc, name, len)
|
87
|
+
end
|
88
|
+
|
89
|
+
def raw desc=nil, name=nil
|
90
|
+
@is_raw = true
|
91
|
+
fields << Raw.new(self, desc, name)
|
92
|
+
end
|
93
|
+
|
94
|
+
def is_raw?
|
95
|
+
@is_raw == true
|
96
|
+
end
|
97
|
+
|
98
|
+
# for constructed tlv's, add subtags that must be present
|
99
|
+
end # meta class thingie
|
100
|
+
|
101
|
+
def display_name
|
102
|
+
self.class.display_name
|
103
|
+
end
|
104
|
+
def to_s
|
105
|
+
longest = 0
|
106
|
+
fields.each { |field|
|
107
|
+
longest = field.display_name.length if field.display_name.length > longest
|
108
|
+
}
|
109
|
+
fmt = "%#{longest}s : %s\n"
|
110
|
+
str = "#{display_name}"
|
111
|
+
str << " (0x#{TLV.b2s(tag)})" if tag
|
112
|
+
str << "\n"
|
113
|
+
|
114
|
+
str << "-" * (str.length-1) << "\n"
|
115
|
+
fields.each { |field|
|
116
|
+
str << (fmt % [field.display_name, TLV.b2s(self.send(field.name))])
|
117
|
+
}
|
118
|
+
(mandatory+optional).each { |tlv_class|
|
119
|
+
temp_tlv = self.send(tlv_class.accessor_name)
|
120
|
+
temp = temp_tlv.to_s
|
121
|
+
temp.gsub!(/^/, " ")
|
122
|
+
str << temp
|
123
|
+
}
|
124
|
+
str
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def fields
|
129
|
+
self.class.fields
|
130
|
+
end
|
131
|
+
|
132
|
+
def mandatory
|
133
|
+
self.class.mand_tags
|
134
|
+
end
|
135
|
+
def optional
|
136
|
+
self.class.opt_tags
|
137
|
+
end
|
138
|
+
|
139
|
+
def tag
|
140
|
+
self.class.tag
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end # module
|
data/lib/tlv/to_bytes.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
module TLV
|
2
|
+
class TLV
|
3
|
+
|
4
|
+
def get_bytes
|
5
|
+
if self.class.primitive? || (self.is_a?(DGI) && fields.length!=0) || self.class.is_raw?
|
6
|
+
bytes = get_bytes_primitive
|
7
|
+
else
|
8
|
+
bytes = get_bytes_constructed
|
9
|
+
end
|
10
|
+
end
|
11
|
+
def get_bytes_primitive
|
12
|
+
bytes = ""
|
13
|
+
fields.each { |field|
|
14
|
+
bytes << self.send(field.name)
|
15
|
+
}
|
16
|
+
bytes
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_bytes_constructed
|
20
|
+
bytes = ""
|
21
|
+
mandatory.each {|t|
|
22
|
+
tlv = self.send(t.accessor_name)
|
23
|
+
raise "Mandatory subtag #{t} not set!" unless tlv
|
24
|
+
bytes << tlv.to_b
|
25
|
+
}
|
26
|
+
optional.each { |t|
|
27
|
+
tlv = self.send(t.accessor_name)
|
28
|
+
bytes << tlv.to_b if tlv
|
29
|
+
}
|
30
|
+
bytes
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_len_bytes len
|
34
|
+
|
35
|
+
# one byte max = 127
|
36
|
+
# two = 255 (2**8)-1
|
37
|
+
# three = 65535 (2 ** 16) -1
|
38
|
+
# four = 16777215 (2 ** 32) -1
|
39
|
+
num_len_bytes = case len
|
40
|
+
when 0..127 : 1
|
41
|
+
when 128..255 : 2
|
42
|
+
when 256..65535 : 3 # short
|
43
|
+
when 65536..4294967295 : 5 # long, skip 3 byte len, too difficult :)
|
44
|
+
else
|
45
|
+
raise "Don't be silly"
|
46
|
+
end
|
47
|
+
len_bytes = case num_len_bytes
|
48
|
+
when 1 : "" << len
|
49
|
+
when 2 : "\x81" << len
|
50
|
+
when 3 : "\x82" << [len].pack("n")
|
51
|
+
when 5 : "\x84" << [len].pack("N")
|
52
|
+
else
|
53
|
+
raise "Can't happen"
|
54
|
+
end
|
55
|
+
return len_bytes
|
56
|
+
end
|
57
|
+
|
58
|
+
# this provides an opportunity to manipulate the payload
|
59
|
+
# before it is assembled. Expects a proc taking the bytes of the
|
60
|
+
# payload and returning the new version of the bytes.
|
61
|
+
def payload_hook= hook
|
62
|
+
@hook = hook
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_b
|
66
|
+
bytes = get_bytes
|
67
|
+
bytes = @hook.call(bytes) if @hook
|
68
|
+
if tag
|
69
|
+
bytes.insert 0, get_len_bytes(bytes.length)
|
70
|
+
bytes.insert 0, tag
|
71
|
+
end
|
72
|
+
bytes
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end # module
|
76
|
+
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/tlv'
|
3
|
+
|
4
|
+
class TestTLVConstructed < Test::Unit::TestCase
|
5
|
+
include TLV
|
6
|
+
def setup
|
7
|
+
end
|
8
|
+
|
9
|
+
class TLVTagTest < TLV
|
10
|
+
tlv "41", "Test TLV"
|
11
|
+
b 8, "first field", :first
|
12
|
+
b 8, "second field", :second
|
13
|
+
end
|
14
|
+
class TLVTestCons < TLV
|
15
|
+
tlv "32", "Test Constructed"
|
16
|
+
mandatory TLVTagTest, "tlv_tag_test"
|
17
|
+
mandatory :tag => "43",
|
18
|
+
:display_name => "Another Test"
|
19
|
+
end
|
20
|
+
|
21
|
+
class TLVTestCons2 < TLV
|
22
|
+
tlv "32", "Test Constructed 32 2"
|
23
|
+
optional TLVTagTest, "tlv_tag_test"
|
24
|
+
optional :tag => "43",
|
25
|
+
:display_name => "Another Test"
|
26
|
+
end
|
27
|
+
|
28
|
+
class DGITest < DGI
|
29
|
+
tlv "9102", "Random Test Data"
|
30
|
+
mandatory TLVTagTest, :test
|
31
|
+
optional :tag => "43",
|
32
|
+
:display_name => "Another Test"
|
33
|
+
end
|
34
|
+
|
35
|
+
def basics t
|
36
|
+
assert(t.methods.include?("tlv_tag_test" ))
|
37
|
+
assert(t.methods.include?("tlv_tag_test="))
|
38
|
+
assert(t.methods.include?("another_test" ))
|
39
|
+
assert(t.methods.include?("another_test="))
|
40
|
+
end
|
41
|
+
def test_basics
|
42
|
+
t = TLVTestCons.new
|
43
|
+
basics t
|
44
|
+
assert_nothing_raised {
|
45
|
+
t = TLVTestCons::AnotherTest.new
|
46
|
+
}
|
47
|
+
t = TLVTestCons2.new
|
48
|
+
basics t
|
49
|
+
assert_nothing_raised {
|
50
|
+
t = TLVTestCons2::AnotherTest.new
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_const_to_b
|
55
|
+
t = TLVTagTest.new
|
56
|
+
t.first = "\x01"
|
57
|
+
t.second = "\x02"
|
58
|
+
|
59
|
+
t2 = TLVTestCons::AnotherTest.new
|
60
|
+
t2.value = "\x34\x56"
|
61
|
+
|
62
|
+
t3 = TLVTestCons.new
|
63
|
+
t3.tlv_tag_test= t
|
64
|
+
t3.another_test= t2
|
65
|
+
|
66
|
+
assert_equal(TLV.s2b("32084102010243023456"), t3.to_b)
|
67
|
+
|
68
|
+
bytes = t3.to_b
|
69
|
+
t, rest = TLVTestCons._parse bytes*2
|
70
|
+
assert_equal bytes, rest
|
71
|
+
assert_equal "\x01", t.tlv_tag_test.first
|
72
|
+
assert_equal "\x02", t.tlv_tag_test.second
|
73
|
+
assert_equal "\x34\x56", t.another_test.value
|
74
|
+
|
75
|
+
t, rest = TLV._parse rest
|
76
|
+
assert_equal "\x01", t.tlv_tag_test.first
|
77
|
+
assert_equal "\x02", t.tlv_tag_test.second
|
78
|
+
assert_equal "\x34\x56", t.another_test.value
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_const_direct_value
|
82
|
+
t = TLVTagTest.new
|
83
|
+
t.first = "\x01"
|
84
|
+
t.second = "\x02"
|
85
|
+
|
86
|
+
t2 = TLVTestCons.new
|
87
|
+
t2.tlv_tag_test=t
|
88
|
+
t2.another_test="\x34\x56"
|
89
|
+
|
90
|
+
assert_equal(TLV.s2b("32084102010243023456"), t2.to_b)
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_const_dgi
|
95
|
+
te = TLVTagTest.new
|
96
|
+
te.first= "\x01"
|
97
|
+
te.second= "\x02"
|
98
|
+
dgi = DGITest.new
|
99
|
+
dgi.test= te
|
100
|
+
dgi.another_test="\x34\x56"
|
101
|
+
|
102
|
+
assert_equal(TLV.s2b("9102084102010243023456"), dgi.to_b)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
end
|
data/test/dgi_test.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.dirname(__FILE__) + '/../lib/tlv'
|
3
|
+
|
4
|
+
class TestDGI < Test::Unit::TestCase
|
5
|
+
include TLV
|
6
|
+
def setup
|
7
|
+
end
|
8
|
+
|
9
|
+
class TLVTest < DGI
|
10
|
+
tlv "0101", "Test TLV"
|
11
|
+
b 8, "first field", :first
|
12
|
+
b 8, "second field", :second
|
13
|
+
end
|
14
|
+
|
15
|
+
class TLVTest2 < DGI
|
16
|
+
tlv "0102", "Test Rubify"
|
17
|
+
b 8, "My Test"
|
18
|
+
b 8, "Oh M@i!"
|
19
|
+
end
|
20
|
+
|
21
|
+
class TLVTest3 < DGI
|
22
|
+
tlv "9F00", "Test Raw"
|
23
|
+
raw
|
24
|
+
end
|
25
|
+
class TLVTestNoTag < DGI
|
26
|
+
b 8, "first field", :first
|
27
|
+
b 8, "second field", :second
|
28
|
+
end
|
29
|
+
|
30
|
+
def basics tlv
|
31
|
+
tlv.first="\x01"
|
32
|
+
tlv.second="\xAA"
|
33
|
+
assert_equal "\x01", tlv.first
|
34
|
+
assert_equal "\xaa", tlv.second
|
35
|
+
|
36
|
+
assert_raise(RuntimeError) {
|
37
|
+
tlv.first="\x02\x03"
|
38
|
+
}
|
39
|
+
assert_raise(RuntimeError) {
|
40
|
+
tlv.first=Time.new
|
41
|
+
}
|
42
|
+
assert_raise(RuntimeError) {
|
43
|
+
tlv.second=1
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_basics
|
48
|
+
t = TLVTest.new
|
49
|
+
basics t
|
50
|
+
assert_equal "\x01\x01\x02\x01\xaa", t.to_b
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_parse_tag
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_length
|
59
|
+
t = TLVTest3.new
|
60
|
+
t.value = ""
|
61
|
+
assert_equal "\x00", t.to_b[2,1]
|
62
|
+
t.value = "1"
|
63
|
+
assert_equal "\x01\x31", t.to_b[2,2]
|
64
|
+
t.value = "1"*127
|
65
|
+
assert_equal "\x7F\x31", t.to_b[2,2]
|
66
|
+
t.value = "1"*128
|
67
|
+
assert_equal "\x80\x31", t.to_b[2,2]
|
68
|
+
t.value = "1"*255
|
69
|
+
assert_equal "\xFf\x00\xff\x31", t.to_b[2,4]
|
70
|
+
t.value = "1"*256
|
71
|
+
assert_equal "\xFF\x01\x00\x31", t.to_b[2,4]
|
72
|
+
|
73
|
+
assert_raises (RuntimeError) {
|
74
|
+
t.value = "1"*65535
|
75
|
+
assert_equal "\x82\xFF\xFF\x31", t.to_b[2,4]
|
76
|
+
}
|
77
|
+
|
78
|
+
o = Object.new
|
79
|
+
def o.length
|
80
|
+
return 4294967296
|
81
|
+
end
|
82
|
+
|
83
|
+
assert_raises (RuntimeError) {
|
84
|
+
t.value=o
|
85
|
+
t.to_b
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_parse
|
90
|
+
t = TLVTest.new
|
91
|
+
assert_equal "\x00", t.first
|
92
|
+
t.first="\x01"
|
93
|
+
t.second="\xAA"
|
94
|
+
|
95
|
+
assert "\x01", t.first
|
96
|
+
bytes = t.to_b
|
97
|
+
# t, rest = TLVTest.parse bytes
|
98
|
+
# assert_equal TLVTest, t.class
|
99
|
+
# assert_equal "\x01", t.first
|
100
|
+
# assert_equal "\xAA", t.second
|
101
|
+
end
|
102
|
+
def test_rubify
|
103
|
+
t = TLVTest2.new
|
104
|
+
t.my_test = "\x01"
|
105
|
+
assert_equal "\x01", t.my_test
|
106
|
+
t.oh_mi = "\x02"
|
107
|
+
assert_equal "\x02", t.oh_mi
|
108
|
+
end
|
109
|
+
def test_raw
|
110
|
+
t = TLVTest3.new
|
111
|
+
#puts t.methods.sort
|
112
|
+
t.value= "bumsi"
|
113
|
+
assert_equal "Test Raw", TLVTest3.display_name
|
114
|
+
assert_equal "bumsi", t.value
|
115
|
+
bytes = t.to_b
|
116
|
+
# t, rest = TLVTest3.parse bytes
|
117
|
+
# assert_equal "bumsi", t.value
|
118
|
+
# assert_equal TLVTest3, t.class
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
end
|