tlv 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|