bindata 2.4.15 → 2.5.1
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 +4 -4
- data/ChangeLog.rdoc +16 -0
- data/README.md +7 -10
- data/bindata.gemspec +5 -4
- data/examples/list.rb +1 -1
- data/lib/bindata/alignment.rb +15 -7
- data/lib/bindata/array.rb +54 -54
- data/lib/bindata/base.rb +14 -25
- data/lib/bindata/base_primitive.rb +24 -20
- data/lib/bindata/bits.rb +5 -5
- data/lib/bindata/buffer.rb +89 -11
- data/lib/bindata/choice.rb +9 -6
- data/lib/bindata/count_bytes_remaining.rb +1 -1
- data/lib/bindata/delayed_io.rb +10 -10
- data/lib/bindata/dsl.rb +34 -32
- data/lib/bindata/float.rb +3 -3
- data/lib/bindata/framework.rb +8 -10
- data/lib/bindata/int.rb +9 -9
- data/lib/bindata/io.rb +276 -253
- data/lib/bindata/name.rb +1 -1
- data/lib/bindata/params.rb +9 -7
- data/lib/bindata/primitive.rb +3 -3
- data/lib/bindata/registry.rb +46 -51
- data/lib/bindata/rest.rb +1 -1
- data/lib/bindata/sanitize.rb +9 -16
- data/lib/bindata/section.rb +97 -0
- data/lib/bindata/skip.rb +140 -51
- data/lib/bindata/string.rb +9 -9
- data/lib/bindata/stringz.rb +12 -10
- data/lib/bindata/struct.rb +83 -66
- data/lib/bindata/trace.rb +35 -42
- data/lib/bindata/transform/brotli.rb +35 -0
- data/lib/bindata/transform/lz4.rb +35 -0
- data/lib/bindata/transform/lzma.rb +35 -0
- data/lib/bindata/transform/xor.rb +19 -0
- data/lib/bindata/transform/xz.rb +35 -0
- data/lib/bindata/transform/zlib.rb +33 -0
- data/lib/bindata/transform/zstd.rb +35 -0
- data/lib/bindata/uint8_array.rb +2 -2
- data/lib/bindata/version.rb +1 -1
- data/lib/bindata/virtual.rb +4 -7
- data/lib/bindata/warnings.rb +1 -1
- data/lib/bindata.rb +3 -2
- data/test/array_test.rb +10 -8
- data/test/buffer_test.rb +9 -0
- data/test/choice_test.rb +1 -1
- data/test/delayed_io_test.rb +16 -0
- data/test/io_test.rb +54 -246
- data/test/registry_test.rb +1 -1
- data/test/section_test.rb +111 -0
- data/test/skip_test.rb +55 -10
- data/test/string_test.rb +4 -4
- data/test/stringz_test.rb +8 -0
- data/test/struct_test.rb +87 -12
- data/test/system_test.rb +119 -1
- data/test/test_helper.rb +30 -15
- data/test/warnings_test.rb +12 -0
- metadata +20 -18
- data/lib/bindata/offset.rb +0 -94
- data/test/offset_test.rb +0 -100
data/lib/bindata/registry.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module BinData
|
2
|
-
|
3
|
-
class UnRegisteredTypeError < StandardError
|
2
|
+
# Raised when #lookup fails.
|
3
|
+
class UnRegisteredTypeError < StandardError; end
|
4
4
|
|
5
5
|
# This registry contains a register of name -> class mappings.
|
6
6
|
#
|
@@ -18,7 +18,6 @@ module BinData
|
|
18
18
|
#
|
19
19
|
# Names are stored in under_score_style, not camelCase.
|
20
20
|
class Registry
|
21
|
-
|
22
21
|
def initialize
|
23
22
|
@registry = {}
|
24
23
|
end
|
@@ -37,55 +36,56 @@ module BinData
|
|
37
36
|
end
|
38
37
|
|
39
38
|
def lookup(name, hints = {})
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
else
|
46
|
-
raise(UnRegisteredTypeError, name)
|
39
|
+
search_names(name, hints).each do |search|
|
40
|
+
register_dynamic_class(search)
|
41
|
+
if @registry.has_key?(search)
|
42
|
+
return @registry[search]
|
43
|
+
end
|
47
44
|
end
|
45
|
+
|
46
|
+
# give the user a hint if the endian keyword is missing
|
47
|
+
search_names(name, hints.merge(endian: :big)).each do |search|
|
48
|
+
register_dynamic_class(search)
|
49
|
+
if @registry.has_key?(search)
|
50
|
+
raise(UnRegisteredTypeError, "#{name}, do you need to specify endian?")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
raise(UnRegisteredTypeError, name)
|
48
55
|
end
|
49
56
|
|
50
57
|
# Convert CamelCase +name+ to underscore style.
|
51
58
|
def underscore_name(name)
|
52
|
-
name
|
53
|
-
to_s
|
54
|
-
sub(/.*::/, "")
|
55
|
-
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
56
|
-
gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
57
|
-
tr(
|
58
|
-
downcase
|
59
|
+
name
|
60
|
+
.to_s
|
61
|
+
.sub(/.*::/, "")
|
62
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
63
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
64
|
+
.tr('-', '_')
|
65
|
+
.downcase
|
59
66
|
end
|
60
67
|
|
61
68
|
#---------------
|
62
69
|
private
|
63
70
|
|
64
|
-
def
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
nwe = name_with_endian(nwp, hints[:endian])
|
77
|
-
if registered?(nwe)
|
78
|
-
name = nwe
|
79
|
-
break
|
80
|
-
end
|
81
|
-
end
|
71
|
+
def search_names(name, hints)
|
72
|
+
base = underscore_name(name)
|
73
|
+
searches = []
|
74
|
+
|
75
|
+
search_prefix = [""] + Array(hints[:search_prefix])
|
76
|
+
search_prefix.each do |prefix|
|
77
|
+
nwp = name_with_prefix(base, prefix)
|
78
|
+
nwe = name_with_endian(nwp, hints[:endian])
|
79
|
+
|
80
|
+
searches << nwp
|
81
|
+
searches << nwe if nwe
|
82
82
|
end
|
83
83
|
|
84
|
-
|
84
|
+
searches
|
85
85
|
end
|
86
86
|
|
87
87
|
def name_with_prefix(name, prefix)
|
88
|
-
prefix = prefix.to_s.chomp(
|
88
|
+
prefix = prefix.to_s.chomp('_')
|
89
89
|
if prefix == ""
|
90
90
|
name
|
91
91
|
else
|
@@ -94,26 +94,21 @@ module BinData
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def name_with_endian(name, endian)
|
97
|
-
return
|
97
|
+
return nil if endian.nil?
|
98
98
|
|
99
|
-
suffix = (endian == :little) ?
|
100
|
-
if /^u?int\d
|
99
|
+
suffix = (endian == :little) ? 'le' : 'be'
|
100
|
+
if /^u?int\d+$/.match?(name)
|
101
101
|
name + suffix
|
102
102
|
else
|
103
|
-
name +
|
103
|
+
name + '_' + suffix
|
104
104
|
end
|
105
105
|
end
|
106
106
|
|
107
|
-
def registered?(name)
|
108
|
-
register_dynamic_class(name) unless @registry.key?(name)
|
109
|
-
|
110
|
-
@registry.key?(name)
|
111
|
-
end
|
112
|
-
|
113
107
|
def register_dynamic_class(name)
|
114
|
-
if /^u?int\d+(le|be)
|
108
|
+
if /^u?int\d+(le|be)$/.match?(name) || /^s?bit\d+(le)?$/.match?(name)
|
115
109
|
class_name = name.gsub(/(?:^|_)(.)/) { $1.upcase }
|
116
110
|
begin
|
111
|
+
# call const_get for side effect of creating class
|
117
112
|
BinData.const_get(class_name)
|
118
113
|
rescue NameError
|
119
114
|
end
|
@@ -122,9 +117,9 @@ module BinData
|
|
122
117
|
|
123
118
|
def warn_if_name_is_already_registered(name, class_to_register)
|
124
119
|
prev_class = @registry[name]
|
125
|
-
if
|
126
|
-
warn "warning: replacing registered class #{prev_class} " \
|
127
|
-
|
120
|
+
if prev_class && prev_class != class_to_register
|
121
|
+
Kernel.warn "warning: replacing registered class #{prev_class} " \
|
122
|
+
"with #{class_to_register}"
|
128
123
|
end
|
129
124
|
end
|
130
125
|
end
|
data/lib/bindata/rest.rb
CHANGED
data/lib/bindata/sanitize.rb
CHANGED
@@ -49,14 +49,10 @@ module BinData
|
|
49
49
|
@prototype = SanitizedPrototype.new(field_type, field_params, hints)
|
50
50
|
end
|
51
51
|
|
52
|
-
attr_reader :prototype
|
52
|
+
attr_reader :prototype, :name
|
53
53
|
|
54
54
|
def name_as_sym
|
55
|
-
@name
|
56
|
-
end
|
57
|
-
|
58
|
-
def name
|
59
|
-
@name
|
55
|
+
@name&.to_sym
|
60
56
|
end
|
61
57
|
|
62
58
|
def has_parameter?(param)
|
@@ -74,11 +70,7 @@ module BinData
|
|
74
70
|
|
75
71
|
def initialize(hints, base_fields = nil)
|
76
72
|
@hints = hints
|
77
|
-
|
78
|
-
@fields = base_fields.raw_fields
|
79
|
-
else
|
80
|
-
@fields = []
|
81
|
-
end
|
73
|
+
@fields = base_fields ? base_fields.raw_fields : []
|
82
74
|
end
|
83
75
|
|
84
76
|
def add_field(type, name, params)
|
@@ -179,7 +171,6 @@ module BinData
|
|
179
171
|
# is to recursively sanitize the parameters of an entire BinData object chain
|
180
172
|
# at a single time.
|
181
173
|
class SanitizedParameters < Hash
|
182
|
-
|
183
174
|
# Memoized constants
|
184
175
|
BIG_ENDIAN = SanitizedBigEndian.new
|
185
176
|
LITTLE_ENDIAN = SanitizedLittleEndian.new
|
@@ -210,7 +201,7 @@ module BinData
|
|
210
201
|
sanitize!
|
211
202
|
end
|
212
203
|
|
213
|
-
|
204
|
+
alias has_parameter? key?
|
214
205
|
|
215
206
|
def has_at_least_one_of?(*keys)
|
216
207
|
keys.each do |key|
|
@@ -257,7 +248,9 @@ module BinData
|
|
257
248
|
end
|
258
249
|
|
259
250
|
def sanitize_object_prototype(key)
|
260
|
-
sanitize(key)
|
251
|
+
sanitize(key) do |obj_type, obj_params|
|
252
|
+
create_sanitized_object_prototype(obj_type, obj_params)
|
253
|
+
end
|
261
254
|
end
|
262
255
|
|
263
256
|
def sanitize_fields(key, &block)
|
@@ -306,7 +299,7 @@ module BinData
|
|
306
299
|
end
|
307
300
|
|
308
301
|
def needs_sanitizing?(key)
|
309
|
-
|
302
|
+
has_parameter?(key) && !self[key].is_a?(SanitizedParameter)
|
310
303
|
end
|
311
304
|
|
312
305
|
def ensure_no_nil_values
|
@@ -320,7 +313,7 @@ module BinData
|
|
320
313
|
|
321
314
|
def merge_default_parameters!
|
322
315
|
@the_class.default_parameters.each do |key, value|
|
323
|
-
self[key] = value unless
|
316
|
+
self[key] = value unless has_parameter?(key)
|
324
317
|
end
|
325
318
|
end
|
326
319
|
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'bindata/base'
|
2
|
+
require 'bindata/dsl'
|
3
|
+
|
4
|
+
module BinData
|
5
|
+
# A Section is a layer on top of a stream that transforms the underlying
|
6
|
+
# data. This allows BinData to process a stream that has multiple
|
7
|
+
# encodings. e.g. Some data data is compressed or encrypted.
|
8
|
+
#
|
9
|
+
# require 'bindata'
|
10
|
+
#
|
11
|
+
# class XorTransform < BinData::IO::Transform
|
12
|
+
# def initialize(xor)
|
13
|
+
# super()
|
14
|
+
# @xor = xor
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def read(n)
|
18
|
+
# chain_read(n).bytes.map { |byte| (byte ^ @xor).chr }.join
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# def write(data)
|
22
|
+
# chain_write(data.bytes.map { |byte| (byte ^ @xor).chr }.join)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# obj = BinData::Section.new(transform: -> { XorTransform.new(0xff) },
|
27
|
+
# type: [:string, read_length: 5])
|
28
|
+
#
|
29
|
+
# obj.read("\x97\x9A\x93\x93\x90") #=> "hello"
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# == Parameters
|
33
|
+
#
|
34
|
+
# Parameters may be provided at initialisation to control the behaviour of
|
35
|
+
# an object. These params are:
|
36
|
+
#
|
37
|
+
# <tt>:transform</tt>:: A callable that returns a new BinData::IO::Transform.
|
38
|
+
# <tt>:type</tt>:: The single type inside the buffer. Use a struct if
|
39
|
+
# multiple fields are required.
|
40
|
+
class Section < BinData::Base
|
41
|
+
extend DSLMixin
|
42
|
+
|
43
|
+
dsl_parser :section
|
44
|
+
arg_processor :section
|
45
|
+
|
46
|
+
mandatory_parameters :transform, :type
|
47
|
+
|
48
|
+
def initialize_instance
|
49
|
+
@type = get_parameter(:type).instantiate(nil, self)
|
50
|
+
end
|
51
|
+
|
52
|
+
def clear?
|
53
|
+
@type.clear?
|
54
|
+
end
|
55
|
+
|
56
|
+
def assign(val)
|
57
|
+
@type.assign(val)
|
58
|
+
end
|
59
|
+
|
60
|
+
def snapshot
|
61
|
+
@type.snapshot
|
62
|
+
end
|
63
|
+
|
64
|
+
def respond_to_missing?(symbol, include_all = false) # :nodoc:
|
65
|
+
@type.respond_to?(symbol, include_all) || super
|
66
|
+
end
|
67
|
+
|
68
|
+
def method_missing(symbol, *args, &block) # :nodoc:
|
69
|
+
@type.__send__(symbol, *args, &block)
|
70
|
+
end
|
71
|
+
|
72
|
+
def do_read(io) # :nodoc:
|
73
|
+
io.transform(eval_parameter(:transform)) do |transformed_io, _raw_io|
|
74
|
+
@type.do_read(transformed_io)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def do_write(io) # :nodoc:
|
79
|
+
io.transform(eval_parameter(:transform)) do |transformed_io, _raw_io|
|
80
|
+
@type.do_write(transformed_io)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def do_num_bytes # :nodoc:
|
85
|
+
to_binary_s.size
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class SectionArgProcessor < BaseArgProcessor
|
90
|
+
include MultiFieldArgSeparator
|
91
|
+
|
92
|
+
def sanitize_parameters!(obj_class, params)
|
93
|
+
params.merge!(obj_class.dsl_params)
|
94
|
+
params.sanitize_object_prototype(:type)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/bindata/skip.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
require
|
1
|
+
require 'bindata/base_primitive'
|
2
|
+
require 'bindata/dsl'
|
2
3
|
|
3
4
|
module BinData
|
4
5
|
# Skip will skip over bytes from the input stream. If the stream is not
|
@@ -18,12 +19,14 @@ module BinData
|
|
18
19
|
#
|
19
20
|
#
|
20
21
|
# class B < BinData::Record
|
21
|
-
# skip
|
22
|
-
#
|
22
|
+
# skip do
|
23
|
+
# string read_length: 2, assert: 'ef'
|
24
|
+
# end
|
25
|
+
# string :s, read_length: 5
|
23
26
|
# end
|
24
27
|
#
|
25
28
|
# obj = B.read("abcdefghij")
|
26
|
-
# obj.
|
29
|
+
# obj.s #=> "efghi"
|
27
30
|
#
|
28
31
|
#
|
29
32
|
# == Parameters
|
@@ -33,15 +36,18 @@ module BinData
|
|
33
36
|
#
|
34
37
|
# <tt>:length</tt>:: The number of bytes to skip.
|
35
38
|
# <tt>:to_abs_offset</tt>:: Skips to the given absolute offset.
|
36
|
-
# <tt>:until_valid</tt>:: Skips
|
39
|
+
# <tt>:until_valid</tt>:: Skips until a given byte pattern is matched.
|
37
40
|
# This parameter contains a type that will raise
|
38
41
|
# a BinData::ValidityError unless an acceptable byte
|
39
42
|
# sequence is found. The type is represented by a
|
40
|
-
# Symbol, or if the type is to have params
|
41
|
-
# passed to it, then it should be provided as
|
43
|
+
# Symbol, or if the type is to have params
|
44
|
+
# passed to it, then it should be provided as
|
42
45
|
# <tt>[type_symbol, hash_params]</tt>.
|
43
46
|
#
|
44
47
|
class Skip < BinData::BasePrimitive
|
48
|
+
extend DSLMixin
|
49
|
+
|
50
|
+
dsl_parser :skip
|
45
51
|
arg_processor :skip
|
46
52
|
|
47
53
|
optional_parameters :length, :to_abs_offset, :until_valid
|
@@ -57,10 +63,11 @@ module BinData
|
|
57
63
|
#---------------
|
58
64
|
private
|
59
65
|
|
60
|
-
def value_to_binary_string(
|
66
|
+
def value_to_binary_string(_)
|
61
67
|
len = skip_length
|
62
|
-
if len
|
63
|
-
raise
|
68
|
+
if len.negative?
|
69
|
+
raise ArgumentError,
|
70
|
+
"#{debug_name} attempted to seek backwards by #{len.abs} bytes"
|
64
71
|
end
|
65
72
|
|
66
73
|
"\000" * skip_length
|
@@ -68,66 +75,148 @@ module BinData
|
|
68
75
|
|
69
76
|
def read_and_return_value(io)
|
70
77
|
len = skip_length
|
71
|
-
if len
|
72
|
-
raise
|
78
|
+
if len.negative?
|
79
|
+
raise ArgumentError,
|
80
|
+
"#{debug_name} attempted to seek backwards by #{len.abs} bytes"
|
73
81
|
end
|
74
82
|
|
75
|
-
io.
|
83
|
+
io.skipbytes(len)
|
76
84
|
""
|
77
85
|
end
|
78
86
|
|
79
87
|
def sensible_default
|
80
88
|
""
|
81
89
|
end
|
82
|
-
end
|
83
90
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
"#{obj_class} requires either :length, :to_abs_offset or :until_valid"
|
91
|
+
# Logic for the :length parameter
|
92
|
+
module SkipLengthPlugin
|
93
|
+
def skip_length
|
94
|
+
eval_parameter(:length)
|
89
95
|
end
|
90
|
-
params.must_be_integer(:to_abs_offset, :length)
|
91
|
-
params.sanitize_object_prototype(:until_valid)
|
92
96
|
end
|
93
|
-
end
|
94
97
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
98
|
+
# Logic for the :to_abs_offset parameter
|
99
|
+
module SkipToAbsOffsetPlugin
|
100
|
+
def skip_length
|
101
|
+
eval_parameter(:to_abs_offset) - abs_offset
|
102
|
+
end
|
99
103
|
end
|
100
|
-
end
|
101
104
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
105
|
+
# Logic for the :until_valid parameter
|
106
|
+
module SkipUntilValidPlugin
|
107
|
+
def skip_length
|
108
|
+
@skip_length ||= 0
|
109
|
+
end
|
108
110
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
0
|
114
|
-
end
|
111
|
+
def read_and_return_value(io)
|
112
|
+
prototype = get_parameter(:until_valid)
|
113
|
+
validator = prototype.instantiate(nil, self)
|
114
|
+
fs = fast_search_for_obj(validator)
|
115
115
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
116
|
+
io.transform(ReadaheadIO.new) do |transformed_io, raw_io|
|
117
|
+
pos = 0
|
118
|
+
loop do
|
119
|
+
seek_to_pos(pos, raw_io)
|
120
|
+
validator.clear
|
121
|
+
validator.do_read(transformed_io)
|
122
|
+
break
|
123
|
+
rescue ValidityError
|
124
|
+
pos += 1
|
125
|
+
|
126
|
+
if fs
|
127
|
+
seek_to_pos(pos, raw_io)
|
128
|
+
pos += next_search_index(raw_io, fs)
|
129
|
+
end
|
126
130
|
end
|
127
|
-
|
128
|
-
|
131
|
+
|
132
|
+
seek_to_pos(pos, raw_io)
|
133
|
+
@skip_length = pos
|
129
134
|
end
|
130
135
|
end
|
136
|
+
|
137
|
+
def seek_to_pos(pos, io)
|
138
|
+
io.rollback
|
139
|
+
io.skip(pos)
|
140
|
+
end
|
141
|
+
|
142
|
+
# A fast search has a pattern string at a specific offset.
|
143
|
+
FastSearch = ::Struct.new('FastSearch', :pattern, :offset)
|
144
|
+
|
145
|
+
def fast_search_for(obj)
|
146
|
+
if obj.respond_to?(:asserted_binary_s)
|
147
|
+
FastSearch.new(obj.asserted_binary_s, obj.rel_offset)
|
148
|
+
else
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# If a search object has an +asserted_value+ field then we
|
154
|
+
# perform a faster search for a valid object.
|
155
|
+
def fast_search_for_obj(obj)
|
156
|
+
if BinData::Struct === obj
|
157
|
+
obj.each_pair(true) do |_, field|
|
158
|
+
fs = fast_search_for(field)
|
159
|
+
return fs if fs
|
160
|
+
end
|
161
|
+
elsif BinData::BasePrimitive === obj
|
162
|
+
return fast_search_for(obj)
|
163
|
+
end
|
164
|
+
|
165
|
+
nil
|
166
|
+
end
|
167
|
+
|
168
|
+
SEARCH_SIZE = 100_000
|
169
|
+
|
170
|
+
def next_search_index(io, fs)
|
171
|
+
buffer = binary_string("")
|
172
|
+
|
173
|
+
# start searching at fast_search offset
|
174
|
+
pos = fs.offset
|
175
|
+
io.skip(fs.offset)
|
176
|
+
|
177
|
+
loop do
|
178
|
+
data = io.read(SEARCH_SIZE)
|
179
|
+
raise EOFError, "no match" if data.nil?
|
180
|
+
|
181
|
+
buffer << data
|
182
|
+
index = buffer.index(fs.pattern)
|
183
|
+
if index
|
184
|
+
return pos + index - fs.offset
|
185
|
+
end
|
186
|
+
|
187
|
+
# advance buffer
|
188
|
+
searched = buffer.slice!(0..-fs.pattern.size)
|
189
|
+
pos += searched.size
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
class ReadaheadIO < BinData::IO::Transform
|
194
|
+
def before_transform
|
195
|
+
if !seekable?
|
196
|
+
raise IOError, "readahead is not supported on unseekable streams"
|
197
|
+
end
|
198
|
+
|
199
|
+
@mark = offset
|
200
|
+
end
|
201
|
+
|
202
|
+
def rollback
|
203
|
+
seek_abs(@mark)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
class SkipArgProcessor < BaseArgProcessor
|
210
|
+
def sanitize_parameters!(obj_class, params)
|
211
|
+
params.merge!(obj_class.dsl_params)
|
212
|
+
|
213
|
+
unless params.has_at_least_one_of?(:length, :to_abs_offset, :until_valid)
|
214
|
+
raise ArgumentError,
|
215
|
+
"#{obj_class} requires :length, :to_abs_offset or :until_valid"
|
216
|
+
end
|
217
|
+
|
218
|
+
params.must_be_integer(:to_abs_offset, :length)
|
219
|
+
params.sanitize_object_prototype(:until_valid)
|
131
220
|
end
|
132
221
|
end
|
133
222
|
end
|
data/lib/bindata/string.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'bindata/base_primitive'
|
2
2
|
|
3
3
|
module BinData
|
4
4
|
# A String is a sequence of bytes. This is the same as strings in Ruby 1.8.
|
@@ -121,6 +121,14 @@ module BinData
|
|
121
121
|
def sensible_default
|
122
122
|
""
|
123
123
|
end
|
124
|
+
|
125
|
+
# Warns when reading if :value && no :read_length
|
126
|
+
module WarnNoReadLengthPlugin
|
127
|
+
def read_and_return_value(io)
|
128
|
+
Kernel.warn "#{debug_name} does not have a :read_length parameter - returning empty string"
|
129
|
+
""
|
130
|
+
end
|
131
|
+
end
|
124
132
|
end
|
125
133
|
|
126
134
|
class StringArgProcessor < BaseArgProcessor
|
@@ -142,12 +150,4 @@ module BinData
|
|
142
150
|
pad_byte
|
143
151
|
end
|
144
152
|
end
|
145
|
-
|
146
|
-
# Warns when reading if :value && no :read_length
|
147
|
-
module WarnNoReadLengthPlugin
|
148
|
-
def read_and_return_value(io)
|
149
|
-
warn "#{debug_name} does not have a :read_length parameter - returning empty string"
|
150
|
-
""
|
151
|
-
end
|
152
|
-
end
|
153
153
|
end
|
data/lib/bindata/stringz.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'bindata/base_primitive'
|
2
2
|
|
3
3
|
module BinData
|
4
4
|
# A BinData::Stringz object is a container for a zero ("\0") terminated
|
@@ -25,7 +25,6 @@ module BinData
|
|
25
25
|
# <tt>:max_length</tt>:: The maximum length of the string including the zero
|
26
26
|
# byte.
|
27
27
|
class Stringz < BinData::BasePrimitive
|
28
|
-
|
29
28
|
optional_parameters :max_length
|
30
29
|
|
31
30
|
def assign(val)
|
@@ -47,14 +46,14 @@ module BinData
|
|
47
46
|
|
48
47
|
def read_and_return_value(io)
|
49
48
|
max_length = eval_parameter(:max_length)
|
50
|
-
str = ""
|
49
|
+
str = binary_string("")
|
51
50
|
i = 0
|
52
51
|
ch = nil
|
53
52
|
|
54
53
|
# read until zero byte or we have read in the max number of bytes
|
55
54
|
while ch != "\0" && i != max_length
|
56
55
|
ch = io.readbytes(1)
|
57
|
-
str
|
56
|
+
str << ch
|
58
57
|
i += 1
|
59
58
|
end
|
60
59
|
|
@@ -66,9 +65,15 @@ module BinData
|
|
66
65
|
end
|
67
66
|
|
68
67
|
def trim_and_zero_terminate(str)
|
68
|
+
max_length = eval_parameter(:max_length)
|
69
|
+
if max_length && max_length < 1
|
70
|
+
msg = "max_length must be >= 1 in #{debug_name} (got #{max_length})"
|
71
|
+
raise ArgumentError, msg
|
72
|
+
end
|
73
|
+
|
69
74
|
result = binary_string(str)
|
70
75
|
truncate_after_first_zero_byte!(result)
|
71
|
-
trim_to!(result,
|
76
|
+
trim_to!(result, max_length)
|
72
77
|
append_zero_byte_if_needed!(result)
|
73
78
|
result
|
74
79
|
end
|
@@ -79,16 +84,13 @@ module BinData
|
|
79
84
|
|
80
85
|
def trim_to!(str, max_length = nil)
|
81
86
|
if max_length
|
82
|
-
max_length = 1 if max_length < 1
|
83
87
|
str.slice!(max_length..-1)
|
84
|
-
|
85
|
-
str[-1, 1] = "\0"
|
86
|
-
end
|
88
|
+
str[-1, 1] = "\0" if str.length == max_length
|
87
89
|
end
|
88
90
|
end
|
89
91
|
|
90
92
|
def append_zero_byte_if_needed!(str)
|
91
|
-
if str.
|
93
|
+
if str.empty? || str[-1, 1] != "\0"
|
92
94
|
str << "\0"
|
93
95
|
end
|
94
96
|
end
|