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