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,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
@@ -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
+
@@ -0,0 +1,7 @@
1
+ module BinaryParser
2
+ class NamelessTemplate < TemplateBase
3
+ def initialize(scope)
4
+ @scope = scope
5
+ end
6
+ end
7
+ end
@@ -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