binary_parser 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +239 -0
- data/Rakefile +11 -0
- data/binary_parser.gemspec +33 -0
- data/lib/binary_parser.rb +50 -0
- data/lib/binary_parser/version.rb +3 -0
- data/lib/built_in_template/binary.rb +12 -0
- data/lib/built_in_template/flag.rb +22 -0
- data/lib/built_in_template/uint.rb +9 -0
- data/lib/error.rb +21 -0
- data/lib/general_class/abstract_binary.rb +68 -0
- data/lib/general_class/bit_position.rb +22 -0
- data/lib/general_class/condition.rb +11 -0
- data/lib/general_class/expression.rb +66 -0
- data/lib/loop_list.rb +52 -0
- data/lib/nameless_template.rb +7 -0
- data/lib/scope.rb +87 -0
- data/lib/stream_template_base.rb +129 -0
- data/lib/structure_definition.rb +131 -0
- data/lib/template_base.rb +120 -0
- data/unit_test/general_class/test_abstract_binary.rb +60 -0
- data/unit_test/general_class/test_bit_position.rb +22 -0
- data/unit_test/general_class/test_condition.rb +23 -0
- data/unit_test/general_class/test_expression.rb +22 -0
- data/unit_test/test_scope.rb +191 -0
- data/unit_test/test_stream_template_base.rb +232 -0
- data/unit_test/test_structure_definition.rb +115 -0
- data/unit_test/test_template_base.rb +62 -0
- metadata +108 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class AbstractBinary
|
3
|
+
|
4
|
+
attr_reader :bit_length
|
5
|
+
|
6
|
+
def initialize(binary_string, bit_index=nil, bit_length=nil)
|
7
|
+
unless binary_string.encoding == Encoding::BINARY
|
8
|
+
raise BadBinaryManipulationError, "binary_string's encoding should be" +
|
9
|
+
"ASCII_8BIT(BINARY). This is #{binary_string.encoding}."
|
10
|
+
end
|
11
|
+
@bin_str = binary_string
|
12
|
+
@bit_index = bit_index || 0
|
13
|
+
@bit_length = bit_length || binary_string.length * 8
|
14
|
+
end
|
15
|
+
|
16
|
+
def sub(spec)
|
17
|
+
additional_bit_index = spec[:bit_index].to_i + spec[:byte_index].to_i * 8
|
18
|
+
if additional_bit_index >= @bit_index + @bit_length
|
19
|
+
raise BadBinaryManipulationError, "Impossible index specification of sub binary " +
|
20
|
+
"(bit_index: #{additional_bit_index} on [#{@bit_index}, #{@bit_length}))"
|
21
|
+
end
|
22
|
+
|
23
|
+
if spec[:bit_length] || spec[:byte_length]
|
24
|
+
new_bit_length = spec[:bit_length].to_i + spec[:byte_length].to_i * 8
|
25
|
+
else
|
26
|
+
new_bit_length = @bit_length - additional_bit_index
|
27
|
+
end
|
28
|
+
if additional_bit_index + new_bit_length > @bit_length
|
29
|
+
raise BadBinaryManipulationError, "Impossible length specification of" +
|
30
|
+
"sub binary (bit_index: #{additional_bit_index}, " +
|
31
|
+
"bit_length: #{new_bit_length} on [#{@bit_index}, #{@bit_length}])"
|
32
|
+
end
|
33
|
+
|
34
|
+
return self.class.new(@bin_str, @bit_index + additional_bit_index, new_bit_length)
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_i
|
38
|
+
if @bit_length == 0
|
39
|
+
raise BadBinaryManipulationError, "Cannot convert empty binary into integer."
|
40
|
+
end
|
41
|
+
res, rest_bit, char_pos = 0, @bit_length - 1, @bit_index % 8
|
42
|
+
@bin_str[@bit_index / 8, (@bit_length + @bit_index % 8) / 8 + 1].unpack("C*").each do |char|
|
43
|
+
(char_pos..7).each do |i|
|
44
|
+
res += char[7 - i] * (1 << rest_bit)
|
45
|
+
return res if (rest_bit -= 1) < 0
|
46
|
+
end
|
47
|
+
char_pos = 0
|
48
|
+
end
|
49
|
+
raise ProgramAssertionError, "Failed to convert integer value."
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_chars
|
53
|
+
unless @bit_index % 8 == 0 && @bit_length % 8 == 0
|
54
|
+
raise BadBinaryManipulationError, "Invalid position(from #{@bit_index} bit)" +
|
55
|
+
"and length(#{@bit_length} bit)."
|
56
|
+
end
|
57
|
+
return @bin_str[@bit_index / 8, @bit_length / 8].unpack("C*")
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
unless @bit_index % 8 == 0 && @bit_length % 8 == 0
|
62
|
+
raise BadBinaryManipulationError, "Invalid position(from #{@bit_index} bit) " +
|
63
|
+
"and length(#{@bit_length} bit)."
|
64
|
+
end
|
65
|
+
return @bin_str[@bit_index / 8, @bit_length / 8]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class BitPosition
|
3
|
+
|
4
|
+
attr_reader :imm, :names
|
5
|
+
|
6
|
+
def initialize(imm=0, names=[])
|
7
|
+
@imm, @names = imm, names
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_imm(length)
|
11
|
+
return BitPosition.new(@imm + length, @names)
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_name(name)
|
15
|
+
return BitPosition.new(@imm, @names + [name])
|
16
|
+
end
|
17
|
+
|
18
|
+
def eval(&name_eval_block)
|
19
|
+
return @imm + @names.inject(0){|sum, name| sum + name_eval_block.call(name)}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class Condition
|
3
|
+
def initialize(*var_names, &condition_proc)
|
4
|
+
@var_names, @condition_proc = var_names, condition_proc
|
5
|
+
end
|
6
|
+
|
7
|
+
def eval(&name_eval_proc)
|
8
|
+
return @condition_proc.call(*@var_names.map{|name| name_eval_proc.call(name)})
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class Expression
|
3
|
+
attr_reader :rpn
|
4
|
+
|
5
|
+
def initialize(rpn)
|
6
|
+
@rpn = rpn
|
7
|
+
end
|
8
|
+
|
9
|
+
def +(other)
|
10
|
+
return Expression.new(@rpn + to_rpn(other) + [:__add])
|
11
|
+
end
|
12
|
+
|
13
|
+
def -(other)
|
14
|
+
return Expression.new(@rpn + to_rpn(other) + [:__sub])
|
15
|
+
end
|
16
|
+
|
17
|
+
def *(other)
|
18
|
+
return Expression.new(@rpn + to_rpn(other) + [:__mul])
|
19
|
+
end
|
20
|
+
|
21
|
+
def /(other)
|
22
|
+
return Expression.new(@rpn + to_rpn(other) + [:__div])
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_rpn(other)
|
26
|
+
case other
|
27
|
+
when Integer
|
28
|
+
return [other]
|
29
|
+
when Expression
|
30
|
+
return other.rpn
|
31
|
+
else
|
32
|
+
raise BadManipulationError, "Unknown type of other (#{other.class})."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def variables
|
37
|
+
control_symbols = [:__add, :__sub, :__mul, :__div]
|
38
|
+
return @rpn.select{|token| token.is_a?(Symbol) && !control_symbols.include?(token)}
|
39
|
+
end
|
40
|
+
|
41
|
+
def eval(&name_eval_block)
|
42
|
+
stack, rpn = [], @rpn.dup
|
43
|
+
until rpn.empty?
|
44
|
+
stack << rpn.shift
|
45
|
+
case stack.last
|
46
|
+
when :__add
|
47
|
+
arg = [stack.pop, stack.pop, stack.pop]
|
48
|
+
stack << arg[2] + arg[1]
|
49
|
+
when :__sub
|
50
|
+
arg = [stack.pop, stack.pop, stack.pop]
|
51
|
+
stack << arg[2] - arg[1]
|
52
|
+
when :__mul
|
53
|
+
arg = [stack.pop, stack.pop, stack.pop]
|
54
|
+
stack << arg[2] * arg[1]
|
55
|
+
when :__div
|
56
|
+
arg = [stack.pop, stack.pop, stack.pop]
|
57
|
+
stack << arg[2] / arg[1]
|
58
|
+
when Symbol
|
59
|
+
stack << name_eval_block.call(stack.pop)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
raise ProgramAssertionError, "Cannot calc RPN." unless stack.length == 1
|
63
|
+
return stack.last
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/loop_list.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class LoopList
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def initialize(definition, abstract_binary, parent_scope)
|
6
|
+
list, rest_binary = [], abstract_binary
|
7
|
+
while rest_binary.bit_length > 0
|
8
|
+
scope = Scope.new(definition.structure, rest_binary, parent_scope)
|
9
|
+
if scope.eval_entire_bit_length == 0
|
10
|
+
raise ParsingError, "0 bit-length repetition happens. This means infinite loop."
|
11
|
+
end
|
12
|
+
rest_binary = rest_binary.sub(:bit_index => scope.eval_entire_bit_length)
|
13
|
+
list << NamelessTemplate.new(scope)
|
14
|
+
end
|
15
|
+
@list = list
|
16
|
+
end
|
17
|
+
|
18
|
+
def each(&block)
|
19
|
+
@list.each(&block)
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](index)
|
23
|
+
unless @list[index]
|
24
|
+
raise BadManipulationError, "Index is out of bounds. List size is #{@list.size}." +
|
25
|
+
"You accessed list[#{index}]."
|
26
|
+
end
|
27
|
+
return @list[index]
|
28
|
+
end
|
29
|
+
|
30
|
+
def size
|
31
|
+
return @list.size
|
32
|
+
end
|
33
|
+
|
34
|
+
# String that describes this object.
|
35
|
+
def content_description
|
36
|
+
"list with #{size} elements"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Print all elements' information.
|
40
|
+
# Args:
|
41
|
+
# recursively => Whether print recursively or not. Default is false.
|
42
|
+
# out => Print target. Default is STDOUT.
|
43
|
+
def show(recursively=false, out=STDOUT, depth=0)
|
44
|
+
#out.puts " " * (depth * 2) + "*** LIST with #{size} elements ***"
|
45
|
+
@list.each_with_index do |element, i|
|
46
|
+
out.puts sprintf(" " * (depth * 2) + "%-5s", "[#{i}]")
|
47
|
+
element.show(true, out, depth + 1) if recursively
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
data/lib/scope.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class Scope
|
3
|
+
|
4
|
+
attr_reader :abstract_binary
|
5
|
+
|
6
|
+
def initialize(structure_definition, abstract_binary, parent_scope=nil)
|
7
|
+
@definition = structure_definition
|
8
|
+
@abstract_binary = abstract_binary
|
9
|
+
@parent_scope = parent_scope
|
10
|
+
@data, @ebs, @ebl = {}, {}, {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(name, *args)
|
14
|
+
return load_var(name) if @definition[name]
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def names
|
19
|
+
@definition.names.dup
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_name_defined(name)
|
23
|
+
raise UndefinedError, "Undefined data-name '#{name}'." unless @definition[name]
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_var(name)
|
27
|
+
return @parent_scope.load_var(name) if !@definition[name] && @parent_scope
|
28
|
+
check_name_defined(name)
|
29
|
+
case @definition[name]
|
30
|
+
when StructureDefinition::DataDefinition
|
31
|
+
return @data[name] ||= eval_bit_length(name) == 0 ? nil :
|
32
|
+
@definition[name].klass.new(load_binary(name))
|
33
|
+
when StructureDefinition::LoopDefinition
|
34
|
+
return @data[name] ||= LoopList.new(@definition[name], load_binary(name), self)
|
35
|
+
else
|
36
|
+
raise ProgramAssertionError, "Unknown definition-class '#{@definition[name].class}'."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_binary(name)
|
41
|
+
check_name_defined(name)
|
42
|
+
start = eval_bit_position(name)
|
43
|
+
length = eval_bit_length(name)
|
44
|
+
begin
|
45
|
+
return @abstract_binary.sub(:bit_index => start, :bit_length => length)
|
46
|
+
rescue BadBinaryManipulationError => error
|
47
|
+
raise ParsingError, "Cannot load binary of '#{name}'.\n" +
|
48
|
+
"*** #{error.backtrace.first} ***\n#{error.message}\n"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def eval_bit_position(name)
|
53
|
+
check_name_defined(name)
|
54
|
+
return @ebs[name] ||= @definition[name].bit_position.eval do |name|
|
55
|
+
eval_bit_length(name)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def eval_bit_length(name)
|
60
|
+
check_name_defined(name)
|
61
|
+
return @ebl[name] if @ebl[name]
|
62
|
+
return @ebl[name] = 0 unless @definition[name].conditions.all? do |cond|
|
63
|
+
cond.eval{|name| load_var(name)}
|
64
|
+
end
|
65
|
+
return @ebl[name] ||= @definition[name].bit_length.eval do |var_name|
|
66
|
+
if var_name == :__rest
|
67
|
+
length = @abstract_binary.bit_length - eval_bit_position(name)
|
68
|
+
raise ParsingError, "Binary is too short. (So, 'rest' is failed.)" if length < 0
|
69
|
+
length
|
70
|
+
else
|
71
|
+
val = load_var(var_name)
|
72
|
+
unless val
|
73
|
+
raise ParsingError, "Variable '#{var_name}' assigned to Nil is referenced" +
|
74
|
+
"at the time of resolving bit_length of '#{var_name}'."
|
75
|
+
end
|
76
|
+
val.to_i
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def eval_entire_bit_length
|
82
|
+
return @definition.bit_at.eval do |name|
|
83
|
+
eval_bit_length(name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module BinaryParser
|
2
|
+
class StreamTemplateBase
|
3
|
+
include BuiltInTemplate
|
4
|
+
|
5
|
+
def self.def_stream(byte_length, &definition_proc)
|
6
|
+
@byte_length = byte_length
|
7
|
+
used_method_names = NamelessTemplate.instance_methods + Scope.instance_methods
|
8
|
+
@structure = StructureDefinition.new(used_method_names, &definition_proc)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.Def(byte_length, &definition_proc) def_stream(byte_length, &definition_proc) end
|
12
|
+
|
13
|
+
def self.get_stream_definition
|
14
|
+
raise BadManipulationError, "Stream is undefined." unless @byte_length && @structure
|
15
|
+
return @byte_length, @structure
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(binary_stream, filters=[])
|
19
|
+
@binary_stream = binary_stream
|
20
|
+
@filters = filters
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add fileter to instance (not disruptive)
|
24
|
+
# return: new instance which has filter
|
25
|
+
def filter(&filter_proc)
|
26
|
+
raise BadManipulationError, "Filter Proc isn't given." unless filter_proc
|
27
|
+
return self.class.new(@binary_stream, @filters + [filter_proc])
|
28
|
+
end
|
29
|
+
|
30
|
+
# Get next element from binary-stream.
|
31
|
+
# If instance has filters, the unsatisfied element is to be abandoned
|
32
|
+
# and recursively returns next element.
|
33
|
+
# Special cases:
|
34
|
+
# (1) If rest of binary-stream's length is 0, this method returns nil.
|
35
|
+
# (2) If rest of binary-stream's length is shorter than required,
|
36
|
+
# this method throws BadBinaryManipulationError.
|
37
|
+
def get_next
|
38
|
+
begin
|
39
|
+
if @lookahead
|
40
|
+
scope, @lookahead = @lookahead, nil
|
41
|
+
else
|
42
|
+
byte_length, structure = self.class.get_stream_definition
|
43
|
+
binary = @binary_stream.read(byte_length)
|
44
|
+
return nil unless binary
|
45
|
+
if binary.length < byte_length
|
46
|
+
raise ParsingError, "Stream's rest binary length" +
|
47
|
+
"(#{binary.length} byte) is shorter than required length (#{byte_length} byte)."
|
48
|
+
end
|
49
|
+
abstract_binary = AbstractBinary.new(binary)
|
50
|
+
scope = Scope.new(structure, abstract_binary)
|
51
|
+
end
|
52
|
+
end until @filters.all?{|filter| filter.call(scope)}
|
53
|
+
return NamelessTemplate.new(scope)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Remove elements until finding element which fullfils proc-condition or reaching end of stream.
|
57
|
+
# return: array of removed elements
|
58
|
+
#
|
59
|
+
# Concrete example:
|
60
|
+
# If stream has [F1, F2, T3, F4, ...] and given cond_proc is Proc.new{|a| a == Tx},
|
61
|
+
# seek_top(&cond_proc) returns array of [F1, F2] and then stream has [T3, F4, ...].
|
62
|
+
# In the same way, (1) [T1, F2, ...] => return: [], stream: [T1, F2, ...]
|
63
|
+
# (2) [F1, F2] => return: [F1, F2], stream: [] (end)
|
64
|
+
# (3) [] (end) => return: [], stream: [] (end)
|
65
|
+
def seek_top(&cond_proc)
|
66
|
+
raise BadManipulationError, "Condition Proc isn't given." unless cond_proc
|
67
|
+
abandoned = []
|
68
|
+
until @lookahead && cond_proc.call(@lookahead)
|
69
|
+
if @lookahead
|
70
|
+
abandoned << @lookahead
|
71
|
+
@lookahead = nil
|
72
|
+
end
|
73
|
+
return abandoned unless rest?
|
74
|
+
@lookahead = get_next
|
75
|
+
end
|
76
|
+
return abandoned
|
77
|
+
end
|
78
|
+
|
79
|
+
# Get sequence by specifing head-condition.
|
80
|
+
# Concrete example:
|
81
|
+
# If stream has [F1, T2, F3, F4, T5, ...] and given cond_proc is Proc.new{|a| a == Tx},
|
82
|
+
# get_sequence(&cond_proc) returns array of [T2, F3, F4] and then stream has [T5, ...].
|
83
|
+
#
|
84
|
+
# In the same way, (1) [T1, F2, T3, ...] => return: [T1, F2], stream: [T3, ...]
|
85
|
+
# (2) [T1, T2, F3, ...] => return: [T1], stream: [T2, F3, ...]
|
86
|
+
# (3) [T1, F2] => return: [], stream: [] (end)
|
87
|
+
# (4) [F1, F2] => return: [], stream: [] (end)
|
88
|
+
# (5) [] (end) => return: [], stream: [] (end)
|
89
|
+
#
|
90
|
+
# * But if option-arg "allow_incomplete_sequence" is set as true, above example of (3) is
|
91
|
+
# [T1, F2, F3, F4] => return: [T1, F2, F3, F4], stream: [] (end)
|
92
|
+
def get_sequence(allow_incomplete_sequence = false, &cond_proc)
|
93
|
+
raise BadManipulationError, "Condition Proc isn't given." unless cond_proc
|
94
|
+
seek_top(&cond_proc)
|
95
|
+
return [] unless rest?
|
96
|
+
res = [get_next] + seek_top(&cond_proc)
|
97
|
+
return [] unless rest? || allow_incomplete_sequence
|
98
|
+
return res
|
99
|
+
end
|
100
|
+
|
101
|
+
# Accumulate elements
|
102
|
+
# Concrete example:
|
103
|
+
# If stream has [1, 2, 3, 4, 5, ...] and given reduce_proc is Proc.new{|acc, a| acc + a},
|
104
|
+
# accumulate(0, 6, &reduce_proc) returns array of [1, 2, 3] and then stream has[4, 5, ...].
|
105
|
+
#
|
106
|
+
# Special case: If enough elements don't remain, this method returns nil.
|
107
|
+
# Example: [1, 2, 3], accumulate(0, 7, &reduce_proc) => return: nil, stream: [] (end)
|
108
|
+
def accumulate(init_amount, dest_amount, &reduce_proc)
|
109
|
+
raise BadManipulationError, "Reduce Proc isn't given." unless reduce_proc
|
110
|
+
accumulation, amount = [], init_amount
|
111
|
+
while amount < dest_amount
|
112
|
+
return nil unless rest?
|
113
|
+
accumulation << get_next
|
114
|
+
amount = reduce_proc.call(amount, accumulation.last)
|
115
|
+
end
|
116
|
+
return accumulation
|
117
|
+
end
|
118
|
+
|
119
|
+
# Check whether binary-stream remains or not.
|
120
|
+
def rest?
|
121
|
+
return @lookahead || !@binary_stream.eof?
|
122
|
+
end
|
123
|
+
|
124
|
+
# Simply close binary-stream.
|
125
|
+
def close
|
126
|
+
@binary_stream.close
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|