binary_parser 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,131 @@
1
+ module BinaryParser
2
+ class StructureDefinition
3
+
4
+ DataDefinition = Struct.new(:bit_position, :bit_length, :conditions, :klass)
5
+ LoopDefinition = Struct.new(:bit_position, :bit_length, :conditions, :structure)
6
+
7
+ attr_reader :parent_structure, :bit_at, :names
8
+
9
+ def initialize(method_names=[], parent_structure=nil, &init_proc)
10
+ @method_names = method_names
11
+ @parent_structure = parent_structure
12
+ @bit_at = BitPosition.new
13
+ @data_def, @var = {}, {}
14
+ @conditions, @names = [], []
15
+ instance_eval(&init_proc) if init_proc
16
+ end
17
+
18
+ def data(name, klass, bit_length)
19
+ check_new_def_name(name)
20
+ unless klass.ancestors.include?(TemplateBase)
21
+ raise DefinitionError, "Class #{klass} should be TemplateBase."
22
+ end
23
+ bit_at, bit_length = process_bit_length(bit_length, name)
24
+ @data_def[name] = DataDefinition.new(bit_at, bit_length, @conditions.dup, klass)
25
+ @names << name
26
+ end
27
+
28
+ def SPEND(bit_length, name, &block)
29
+ check_new_def_name(name)
30
+ bit_at, bit_length = process_bit_length(bit_length, name)
31
+ used_method_names = NamelessTemplate.instance_methods + Scope.instance_methods
32
+ structure = StructureDefinition.new(used_method_names, self, &block)
33
+ @data_def[name] = LoopDefinition.new(bit_at, bit_length, @conditions.dup, structure)
34
+ @names << name
35
+ end
36
+
37
+ def TIMES(times, name, &block)
38
+ check_new_def_name(name)
39
+ structure = StructureDefinition.new(Scope.instance_methods, self, &block)
40
+ if structure.bit_at.names.empty?
41
+ bit_at, bit_length = process_bit_length(times * structure.bit_at.imm, name)
42
+ @data_def[name] = LoopDefinition.new(bit_at, bit_length, @conditions.dup, structure)
43
+ else
44
+ bit_length = Expression.new([0])
45
+ structure.bit_at.names.each do |bit_at_depending_name|
46
+ depending_length_exp = structure[bit_at_depending_name].bit_length
47
+ depending_length_exp.variables.each do |var_name|
48
+ if structure[var_name]
49
+ raise DefinitionError, "In '#{name}', same level variable #{var_name} is referenced." +
50
+ "*** TIMES's inner structure's bit-length must be always same." +
51
+ "In other words, that bit-length must not rely on same level variables. ***"
52
+ end
53
+ end
54
+ bit_length += depending_length_exp
55
+ end
56
+ bit_at, bit_length = process_bit_length(bit_length * times, name)
57
+ @data_def[name] = LoopDefinition.new(bit_at, bit_length, @conditions.dup, structure)
58
+ end
59
+ @names << name
60
+ end
61
+
62
+ def IF(condition, &block)
63
+ @conditions.push(condition)
64
+ block.call
65
+ @conditions.pop
66
+ end
67
+
68
+ def check_new_def_name(name)
69
+ if name[0..1] == "__"
70
+ raise DefinitionError, "Name that starts with '__' is system-reserved."
71
+ end
72
+ if @method_names.include?(name)
73
+ raise DefinitionError, "Name '#{name}' is already used as method name." +
74
+ "You should chanege to other name."
75
+ end
76
+ if @data_def[name]
77
+ raise DefinitionError, "Name #{name} is already defined." +
78
+ "You should change to other name."
79
+ end
80
+ end
81
+
82
+ def name_solvable?(name, structure=self)
83
+ return structure[name] ||
84
+ (structure.parent_structure && name_solvable?(name, structure.parent_structure))
85
+ end
86
+
87
+ def cond(*var_names, &condition_proc)
88
+ unless var_names.all?{|name| name_solvable?(name)}
89
+ raise DefinitionError, "As condition variable, unsolvable variable #{symbol} is used."
90
+ end
91
+ return Condition.new(*var_names, &condition_proc)
92
+ end
93
+
94
+ def var(name)
95
+ return @var[name] ||= Expression.new([name])
96
+ end
97
+
98
+ def rest
99
+ return var(:__rest)
100
+ end
101
+
102
+ def [](name)
103
+ return @data_def[name]
104
+ end
105
+
106
+ private
107
+
108
+ def process_bit_length(bit_length, name)
109
+ bit_at = @bit_at
110
+ case bit_length
111
+ when Integer
112
+ if @conditions.empty?
113
+ @bit_at = @bit_at.add_imm(bit_length)
114
+ else
115
+ @bit_at = @bit_at.add_name(name)
116
+ end
117
+ return bit_at, Expression.new([bit_length])
118
+ when Expression
119
+ bit_length.variables.reject{|s| s[0..1] == "__"}.each do |symbol|
120
+ unless name_solvable?(symbol)
121
+ raise DefinitionError, "In #{name}, unsolvable variable #{symbol} is used."
122
+ end
123
+ end
124
+ @bit_at = @bit_at.add_name(name)
125
+ return bit_at, bit_length
126
+ else
127
+ raise DefinitionError, "Unknown type of bit_length (#{bit_length.class})."
128
+ end
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,120 @@
1
+ module BinaryParser
2
+ class TemplateBase
3
+ include BuiltInTemplate
4
+
5
+ def self.def_structure(&definition_proc)
6
+ used_method_names = self.instance_methods + Scope.instance_methods
7
+ @structure_def = StructureDefinition.new(used_method_names, &definition_proc)
8
+ end
9
+
10
+ def self.Def(&definition_proc) def_structure(&definition_proc) end
11
+
12
+ def self.structure
13
+ return @structure_def ||= StructureDefinition.new
14
+ end
15
+
16
+ def initialize(binary)
17
+ @scope = Scope.new(self.class.structure, convert_into_abstract_binary(binary))
18
+ end
19
+
20
+ def convert_into_abstract_binary(object)
21
+ return object if object.is_a?(AbstractBinary)
22
+ if object.is_a?(String) && object.encoding == Encoding::BINARY
23
+ return AbstractBinary.new(object)
24
+ end
25
+ raise BadManipulationError, "Argument should be AbstractBinary or BINAY String."
26
+ end
27
+
28
+ def method_missing(name, *args)
29
+ return @scope.method_missing(name, *args)
30
+ end
31
+
32
+ def [](name)
33
+ return @scope.method_missing(name)
34
+ end
35
+
36
+ def names
37
+ return @scope.names
38
+ end
39
+
40
+ # Convert held binary into unsigned integer.
41
+ # Special case:
42
+ # If held binary's length is 0, this method throws BadBinaryManipulationError.
43
+ def to_i
44
+ @scope.abstract_binary.to_i
45
+ end
46
+
47
+ # Convert held binary into string encoded in Encoding::BINARY.
48
+ # Special case:
49
+ # If held binary's length or start position isn't a multiple of 8,
50
+ # this method throws BadBinaryManipulationError.
51
+ def to_s
52
+ @scope.abstract_binary.to_s
53
+ end
54
+
55
+ # Convert held binary into character-numbers.
56
+ # Example: If held binary is "ABC" in ascii, this returns [0x41, 0x42, 0x43].
57
+ # Special case:
58
+ # If held binary's length or start position isn't a multiple of 8,
59
+ # this method throws BadBinaryManipulationError.
60
+ def to_chars
61
+ @scope.abstract_binary.to_chars
62
+ end
63
+
64
+ # Real length(bit) of held binary
65
+ def binary_bit_length
66
+ @scope.abstract_binary.bit_length
67
+ end
68
+
69
+ # Structure-specified length(bit) of binary.
70
+ # Special case:
71
+ # If held binary's length is too short to calculate structure-specified length,
72
+ # this method throws ParsingError.
73
+ def structure_bit_length
74
+ @scope.eval_entire_bit_length
75
+ end
76
+
77
+ # Whether real length of held binary is NOT smaller than structure-specified length of binary.
78
+ # Special case:
79
+ # If held binary's length is too short to calculate structure-specified length,
80
+ # this method throws ParsingError.
81
+ def hold_enough_binary?
82
+ structure_bit_length <= binary_bit_length
83
+ end
84
+
85
+ # Whether real length of held binary is equal to structure-specified length of binary.
86
+ # Special case:
87
+ # If held binary's length is too short to calculate structure-specified length,
88
+ # this method throws ParsingError.
89
+ def hold_just_binary?
90
+ structure_bit_length == binary_bit_length
91
+ end
92
+
93
+ # String that describes this object.
94
+ # * If you want to print some of this content-description in 'show' method,
95
+ # override this method.
96
+ def content_description
97
+ ""
98
+ end
99
+
100
+ # Print all elements' information.
101
+ # Args:
102
+ # recursively => Whether print recursively or not. Default is false.
103
+ # out => Print target. Default is STDOUT.
104
+ def show(recursively=false, out=STDOUT, depth=0)
105
+ max_name_length = names.inject(5){|max_len, name| [max_len, name.length].max}
106
+ if names.size > 0
107
+ out.puts "#{" " * (depth*2)}*#{"-" * 80}"
108
+ end
109
+ names.each do |name|
110
+ out.puts sprintf("#{" " * (depth*2)}%-#{max_name_length}s Pos: %6s Len: %6s Type: %10s Cont: %s",
111
+ name.to_s,
112
+ @scope.eval_bit_position(name),
113
+ @scope.eval_bit_length(name),
114
+ self[name].class.name.split("::").last,
115
+ self[name] ? self[name].content_description : "Nil")
116
+ self[name].show(true, out, depth + 1) if recursively && self[name]
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,60 @@
1
+ # -*- coding: utf-8 -*-
2
+ $LIBRARY_ROOT_PATH = File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__))))
3
+
4
+ module BinaryParser
5
+ module UnitTest
6
+ require 'test/unit'
7
+
8
+ # load testing target
9
+ require $LIBRARY_ROOT_PATH + '/lib/general_class/abstract_binary.rb'
10
+
11
+ class AbstractBinaryTest < Test::Unit::TestCase
12
+
13
+ def test_to_i
14
+ abin = AbstractBinary.new(gen_bin(0b10101010, 0b01010101))
15
+ assert_equal(0b1010101001010101, abin.to_i)
16
+ end
17
+
18
+ def test_to_chars
19
+ abin = AbstractBinary.new(gen_bin(0b10101010, 0b01010101))
20
+ assert_equal(0b10101010, abin.to_chars[0])
21
+ assert_equal(0b01010101, abin.to_chars[1])
22
+ end
23
+
24
+ def test_to_s
25
+ abin = AbstractBinary.new(gen_bin(0x48, 0x4f, 0x47, 0x45))
26
+ assert_equal("HOGE", abin.to_s)
27
+ end
28
+
29
+ def test_sub
30
+ abin = AbstractBinary.new(gen_bin(0b11110000, 0b11110000, 0b11110001))
31
+ assert_equal(0b110, abin.sub(:bit_index => 2, :bit_length => 3).to_i)
32
+ assert_equal([0b11110000, 0b11110001], abin.sub(:bit_index => 6).sub(:bit_index => 2).to_chars)
33
+ end
34
+
35
+ def test_to_chars_error
36
+ abin = AbstractBinary.new(gen_bin(0b11110000, 0b11111111))
37
+ sub = abin.sub(:bit_index => 4, :bit_length => 8)
38
+ assert_equal(0b00001111, sub.to_i)
39
+ assert_raise(BadBinaryManipulationError) do
40
+ sub.to_chars
41
+ end
42
+ end
43
+
44
+ def test_sub_error
45
+ abin = AbstractBinary.new(gen_bin(0b11110000, 0b11111111))
46
+ assert_raise(BadBinaryManipulationError) do
47
+ abin.sub(:bit_index => 16)
48
+ end
49
+ assert_raise(BadBinaryManipulationError) do
50
+ abin.sub(:bit_length => 17)
51
+ end
52
+ end
53
+
54
+ # helper for generating binary
55
+ def gen_bin(*chars)
56
+ return chars.pack("C*")
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ $LIBRARY_ROOT_PATH = File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__))))
3
+
4
+ module BinaryParser
5
+ module UnitTest
6
+ require 'test/unit'
7
+
8
+ # load testing target
9
+ require $LIBRARY_ROOT_PATH + '/lib/general_class/bit_position.rb'
10
+
11
+ class BitPositionTest < Test::Unit::TestCase
12
+
13
+ VAL = {:hoge => 10, :fuga => 1000}
14
+
15
+ def test_bit_position
16
+ bp = BitPosition.new
17
+ pos = bp.add_imm(4).add_name(:hoge).add_imm(3).add_name(:fuga).eval{|name| VAL[name]}
18
+ assert_equal(1017, pos)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+ $LIBRARY_ROOT_PATH = File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__))))
3
+
4
+ module BinaryParser
5
+ module UnitTest
6
+ require 'test/unit'
7
+
8
+ # load testing target
9
+ require $LIBRARY_ROOT_PATH + '/lib/general_class/condition.rb'
10
+
11
+ class ConditionTest < Test::Unit::TestCase
12
+
13
+ VAL = {:hoge => 10, :fuga => 1000}
14
+
15
+ def test_condition
16
+ cond1 = Condition.new(:hoge, :fuga){|v1, v2| v1 * 100 == v2}
17
+ cond2 = Condition.new(:hoge, :fuga){|v1, v2| v1 * 10 == v2}
18
+ assert_equal(true, cond1.eval{|name| VAL[name]})
19
+ assert_equal(false, cond2.eval{|name| VAL[name]})
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+ $LIBRARY_ROOT_PATH = File.dirname(File.dirname(File.expand_path(File.dirname(__FILE__))))
3
+
4
+ module BinaryParser
5
+ module UnitTest
6
+ require 'test/unit'
7
+
8
+ # load testing target
9
+ require $LIBRARY_ROOT_PATH + '/lib/general_class/expression.rb'
10
+
11
+ class ExpressionTest < Test::Unit::TestCase
12
+ VAL = {:hoge => 10, :fuga => 1000}
13
+
14
+ def test_expression
15
+ var_exp1 = Expression.new([:hoge])
16
+ var_exp2 = Expression.new([:fuga])
17
+ exp = (var_exp1 + 3) * 12 + var_exp2 / 10 - 4
18
+ assert_equal((10 + 3) * 12 + 1000 / 10 - 4, exp.eval{|name| VAL[name]})
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,191 @@
1
+ # -*- coding: utf-8 -*-
2
+ $LIBRARY_ROOT_PATH = File.dirname(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ module BinaryParser
5
+ module UnitTest
6
+ require 'test/unit'
7
+
8
+ # load testing target
9
+ require $LIBRARY_ROOT_PATH + '/lib/binary_parser.rb'
10
+
11
+ class ScopeTest < Test::Unit::TestCase
12
+
13
+ # TEST CASE STRUCTURE 1
14
+ # * roughly test various
15
+ class ST1 < TemplateBase
16
+ Def do
17
+ data :cond_num, UInt, 8
18
+ IF cond(:cond_num){|v| v.to_i == 1} do
19
+ data :length, UInt, 8
20
+ data :data, Binary, var(:length) * 8
21
+ end
22
+
23
+ data :binary_len, UInt, 8
24
+ SPEND var(:binary_len) * 8, :list do
25
+ data :length, UInt, 8
26
+ data :data, Binary, var(:length) * 8
27
+ end
28
+
29
+ data :foot, UInt, 32
30
+ end
31
+ end
32
+
33
+ def test_ST1_CASE1
34
+ bin = gen_bin(*[0x01, [0x04, [0x48, 0x4f, 0x47, 0x45]],
35
+ 0x06, [0x1, [0x70], 0x3, [0x69, 0x79, 0x6f]],
36
+ 0xff, 0xff, 0xff, 0xff].flatten)
37
+
38
+ sc = ST1.new(bin)
39
+
40
+ assert_equal(1, sc.cond_num.to_i)
41
+ assert_equal(4, sc.length.to_i)
42
+ assert_equal("HOGE", sc.data.to_s)
43
+
44
+ assert_equal(6, sc.binary_len.to_i)
45
+ assert_equal(2, sc.list.size)
46
+ assert_equal("p", sc.list[0].data.to_s)
47
+ assert_equal("iyo", sc.list[1].data.to_s)
48
+
49
+ assert_equal(0xffffffff, sc.foot.to_i)
50
+ end
51
+
52
+ # TEST CASE STRUCTURE 2
53
+ # * about IF
54
+ class ST2 < TemplateBase
55
+ Def do
56
+ data :number1, UInt, 8
57
+ IF cond(:number1){|v| v.to_i == 1} do
58
+ data :number2, UInt, 8
59
+ IF cond(:number2){|v| v.to_i == 2} do
60
+ data :number3, UInt, var(:number2) * 4
61
+ end
62
+ end
63
+ data :number4, UInt, 8
64
+ end
65
+ end
66
+
67
+ def test_ST2_CASE1
68
+ bin = gen_bin(1, 2, 3, 4)
69
+ i = ST2.new(bin)
70
+
71
+ assert_equal(1, i.number1.to_i)
72
+ assert_equal(2, i.number2.to_i)
73
+ assert_equal(3, i.number3.to_i)
74
+ assert_equal(4, i.number4.to_i)
75
+ assert(i.hold_just_binary?)
76
+ end
77
+
78
+ def test_ST2_CASE2
79
+ bin = gen_bin(1, 0, 4)
80
+ i = ST2.new(bin)
81
+
82
+ assert_equal(1, i.number1.to_i)
83
+ assert_equal(0, i.number2.to_i)
84
+ assert_equal(nil, i.number3)
85
+ assert_equal(4, i.number4.to_i)
86
+ assert(i.hold_just_binary?)
87
+ end
88
+
89
+ def test_ST2_CASE3
90
+ bin = gen_bin(0, 4)
91
+ i = ST2.new(bin)
92
+
93
+ assert_equal(0, i.number1.to_i)
94
+ assert_equal(nil, i.number2)
95
+ assert_equal(nil, i.number3)
96
+ assert_equal(4, i.number4.to_i)
97
+ assert(i.hold_just_binary?)
98
+ end
99
+
100
+ # TEST CASE STRUCTURE 3
101
+ # * SPEND and TIMES
102
+ class ST3 < TemplateBase
103
+ Def do
104
+ data :size, UInt, 8
105
+ SPEND var(:size), :l1 do
106
+ data :dat, UInt, 8
107
+ end
108
+ data :times, UInt, 8
109
+ TIMES var(:times), :l2 do
110
+ data :dat, UInt, 8
111
+ end
112
+ data :dat, UInt, 8
113
+ end
114
+ end
115
+
116
+ def test_ST3_CASE1
117
+ bin = gen_bin(0, 0, 0xff)
118
+ i = ST3.new(bin)
119
+
120
+ assert_equal(0, i.size.to_i)
121
+ assert_equal(0, i.l1.size)
122
+ assert_equal(0, i.times.to_i)
123
+ assert_equal(0, i.l2.size)
124
+ assert_equal(0xff, i.dat.to_i)
125
+ assert(i.hold_just_binary?)
126
+ end
127
+
128
+ # TEST CASE STRUCTURE 4
129
+ # * Dynamic length test
130
+ class ST4 < TemplateBase
131
+ Def do
132
+ data :v1, UInt, 8
133
+ data :v2, UInt, var(:v1)
134
+ data :v3, UInt, var(:v2)
135
+ data :v4, UInt, rest
136
+ data :v5, UInt, rest
137
+ end
138
+ end
139
+
140
+ def test_ST4_CASE1
141
+ bin = gen_bin(8, 0, 0xff, 0xff)
142
+ i = ST4.new(bin)
143
+
144
+ assert_equal(8, i.v1.to_i)
145
+ assert_equal(0, i.v2.to_i)
146
+ assert_equal(nil, i.v3)
147
+ assert_equal(0xffff, i.v4.to_i)
148
+ assert_equal(nil, i.v5)
149
+ assert(i.hold_just_binary?)
150
+ end
151
+
152
+ def test_ST4_CASE2
153
+ bin = gen_bin(8, 8, 0xff)
154
+ i = ST4.new(bin)
155
+
156
+ assert_equal(8, i.v1.to_i)
157
+ assert_equal(8, i.v2.to_i)
158
+ assert_equal(0xff, i.v3.to_i)
159
+ assert_equal(nil, i.v4)
160
+ assert_equal(nil, i.v5)
161
+ assert(i.hold_just_binary?)
162
+ end
163
+
164
+ # TEST CASE STRUCTURE 5
165
+ # * structure size test
166
+ class ST5 < TemplateBase
167
+ Def do
168
+ data :v1, UInt, 8
169
+ data :v2, UInt, var(:v1)
170
+ data :v3, UInt, var(:v2)
171
+ end
172
+ end
173
+
174
+ def test_ST5_CASE1
175
+ i1 = ST5.new(gen_bin(8, 8))
176
+ assert(!i1.hold_enough_binary?)
177
+
178
+ i2 = ST5.new(gen_bin(8))
179
+ assert_raise(ParsingError) do
180
+ assert(!i2.hold_enough_binary?)
181
+ end
182
+ end
183
+
184
+ # helpers
185
+ def gen_bin(*chars)
186
+ return AbstractBinary.new(chars.pack("C*"))
187
+ end
188
+ end
189
+ end
190
+ end
191
+