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