bindata 2.4.15 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ChangeLog.rdoc +11 -0
- data/README.md +6 -9
- data/bindata.gemspec +3 -3
- 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 +18 -18
- 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 +1 -0
- 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 +24 -13
- data/test/warnings_test.rb +12 -0
- metadata +17 -16
- 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
|
@@ -49,13 +48,13 @@ module BinData
|
|
49
48
|
|
50
49
|
# Convert CamelCase +name+ to underscore style.
|
51
50
|
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
|
51
|
+
name
|
52
|
+
.to_s
|
53
|
+
.sub(/.*::/, "")
|
54
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
55
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
56
|
+
.tr('-', '_')
|
57
|
+
.downcase
|
59
58
|
end
|
60
59
|
|
61
60
|
#---------------
|
@@ -65,7 +64,7 @@ module BinData
|
|
65
64
|
name = underscore_name(name)
|
66
65
|
|
67
66
|
if !registered?(name)
|
68
|
-
search_prefix = [""]
|
67
|
+
search_prefix = [""] + Array(hints[:search_prefix])
|
69
68
|
search_prefix.each do |prefix|
|
70
69
|
nwp = name_with_prefix(name, prefix)
|
71
70
|
if registered?(nwp)
|
@@ -85,7 +84,7 @@ module BinData
|
|
85
84
|
end
|
86
85
|
|
87
86
|
def name_with_prefix(name, prefix)
|
88
|
-
prefix = prefix.to_s.chomp(
|
87
|
+
prefix = prefix.to_s.chomp('_')
|
89
88
|
if prefix == ""
|
90
89
|
name
|
91
90
|
else
|
@@ -96,11 +95,11 @@ module BinData
|
|
96
95
|
def name_with_endian(name, endian)
|
97
96
|
return name if endian.nil?
|
98
97
|
|
99
|
-
suffix = (endian == :little) ?
|
100
|
-
if /^u?int\d
|
98
|
+
suffix = (endian == :little) ? 'le' : 'be'
|
99
|
+
if /^u?int\d+$/.match?(name)
|
101
100
|
name + suffix
|
102
101
|
else
|
103
|
-
name +
|
102
|
+
name + '_' + suffix
|
104
103
|
end
|
105
104
|
end
|
106
105
|
|
@@ -111,9 +110,10 @@ module BinData
|
|
111
110
|
end
|
112
111
|
|
113
112
|
def register_dynamic_class(name)
|
114
|
-
if /^u?int\d+(le|be)
|
113
|
+
if /^u?int\d+(le|be)$/.match?(name) || /^s?bit\d+(le)?$/.match?(name)
|
115
114
|
class_name = name.gsub(/(?:^|_)(.)/) { $1.upcase }
|
116
115
|
begin
|
116
|
+
# call const_get for side effects
|
117
117
|
BinData.const_get(class_name)
|
118
118
|
rescue NameError
|
119
119
|
end
|
@@ -122,8 +122,8 @@ module BinData
|
|
122
122
|
|
123
123
|
def warn_if_name_is_already_registered(name, class_to_register)
|
124
124
|
prev_class = @registry[name]
|
125
|
-
if
|
126
|
-
warn "warning: replacing registered class #{prev_class} " \
|
125
|
+
if prev_class && prev_class != class_to_register
|
126
|
+
Kernel.warn "warning: replacing registered class #{prev_class} " \
|
127
127
|
"with #{class_to_register}"
|
128
128
|
end
|
129
129
|
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
|