binary_parser 1.0.0

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.
@@ -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
+