bindata 0.5.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of bindata might be problematic. Click here for more details.

@@ -0,0 +1,98 @@
1
+ require "bindata/single"
2
+
3
+ module BinData
4
+ # A String is a sequence of bytes. This is the same as strings in Ruby.
5
+ # The issue of character encoding is ignored by this class.
6
+ #
7
+ # == Parameters
8
+ #
9
+ # String objects accept all the params that BinData::Single
10
+ # does, as well as the following:
11
+ #
12
+ # <tt>:initial_length</tt>:: The initial length to use before a value is
13
+ # either read or set.
14
+ # <tt>:length</tt>:: The fixed length of the string. If a shorter
15
+ # string is set, it will be padded to this length.
16
+ # <tt>:pad_char</tt>:: The character to use when padding a string to a
17
+ # set length. Valid values are Integers and
18
+ # Strings of length 1. "\0" is the default.
19
+ # <tt>:trim_value</tt>:: Boolean, default false. If set, #value will
20
+ # return the value with all pad_chars trimmed
21
+ # from the end of the string. The value will
22
+ # not be trimmed when writing.
23
+ class String < Single
24
+ # These are the parameters used by this class.
25
+ mandatory_parameters :pad_char
26
+ optional_parameters :initial_length, :length, :trim_value
27
+
28
+ def initialize(params = {}, env = nil)
29
+ super(cleaned_params(params), env)
30
+
31
+ # the only valid param combinations of length and value are:
32
+ # :initial_length and :value
33
+ # :length and :initial_value
34
+ ensure_mutual_exclusion(:initial_value, :value)
35
+ ensure_mutual_exclusion(:initial_length, :length)
36
+ ensure_mutual_exclusion(:initial_length, :initial_value)
37
+ ensure_mutual_exclusion(:length, :value)
38
+ end
39
+
40
+ # Overrides value to return the value padded to the desired length or
41
+ # trimmed as required.
42
+ def value
43
+ v = val_to_str(_value)
44
+ v.sub!(/#{eval_param(:pad_char)}*$/, "") if param(:trim_value) == true
45
+ v
46
+ end
47
+
48
+ #---------------
49
+ private
50
+
51
+ # Returns +val+ ensuring that it is padded to the desired length.
52
+ def val_to_str(val)
53
+ # trim val if necessary
54
+ len = val_num_bytes(val)
55
+ str = val.slice(0, len)
56
+
57
+ # then pad to length if str is short
58
+ str << (eval_param(:pad_char) * (len - str.length))
59
+ end
60
+
61
+ # Read a number of bytes from +io+ and return the value they represent.
62
+ def read_val(io)
63
+ readbytes(io, val_num_bytes(""))
64
+ end
65
+
66
+ # Returns an empty string as default.
67
+ def sensible_default
68
+ ""
69
+ end
70
+
71
+ # Return the number of bytes that +val+ will occupy when written.
72
+ def val_num_bytes(val)
73
+ if clear? and (evaluated = eval_param(:initial_length))
74
+ evaluated
75
+ elsif (evaluated = eval_param(:length))
76
+ evaluated
77
+ else
78
+ val.length
79
+ end
80
+ end
81
+
82
+ # Returns a hash of cleaned +params+. Cleaning means that param
83
+ # values are converted to a desired format.
84
+ def cleaned_params(params)
85
+ new_params = params.dup
86
+
87
+ # set :pad_char to be a single length character string
88
+ ch = new_params[:pad_char] || 0
89
+ ch = ch.respond_to?(:chr) ? ch.chr : ch.to_s
90
+ if ch.length > 1
91
+ raise ArgumentError, ":pad_char must not contain more than 1 char"
92
+ end
93
+ new_params[:pad_char] = ch
94
+
95
+ new_params
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,83 @@
1
+ require "bindata/single"
2
+
3
+ module BinData
4
+ # A BinData::Stringz object is a container for a zero ("\0") terminated
5
+ # string.
6
+ #
7
+ # For convenience, the zero terminator is not necessary when setting the
8
+ # value. Likewise, the returned value will not be zero terminated.
9
+ #
10
+ # == Parameters
11
+ #
12
+ # Stringz objects accept all the params that BinData::Single
13
+ # does, as well as the following:
14
+ #
15
+ # <tt>:max_length</tt>:: The maximum length of the string including the zero
16
+ # byte.
17
+ class Stringz < Single
18
+ # These are the parameters used by this class.
19
+ optional_parameters :max_length
20
+
21
+ # Overrides value to return the value of this data excluding the trailing
22
+ # zero byte.
23
+ def value
24
+ v = super
25
+ val_to_str(v).chomp("\0")
26
+ end
27
+
28
+ #---------------
29
+ private
30
+
31
+ # Returns +val+ ensuring it is zero terminated and no longer
32
+ # than <tt>:max_length</tt> bytes.
33
+ def val_to_str(val)
34
+ zero_terminate(val, eval_param(:max_length))
35
+ end
36
+
37
+ # Read a number of bytes from +io+ and return the value they represent.
38
+ def read_val(io)
39
+ max_length = eval_param(:max_length)
40
+ str = ""
41
+ i = 0
42
+ ch = nil
43
+
44
+ # read until zero byte or we have read in the max number of bytes
45
+ while ch != "\0" and i != max_length
46
+ ch = readbytes(io, 1)
47
+ str << ch
48
+ i += 1
49
+ end
50
+
51
+ zero_terminate(str, max_length)
52
+ end
53
+
54
+ # Returns an empty string as default.
55
+ def sensible_default
56
+ ""
57
+ end
58
+
59
+ # Returns +str+ after it has been zero terminated. The returned string
60
+ # will not be longer than +max_length+.
61
+ def zero_terminate(str, max_length = nil)
62
+ # str must not be empty
63
+ str = "\0" if str == ""
64
+
65
+ # remove anything after the first \0
66
+ str = str.sub(/([^\0]*\0).*/, '\1')
67
+
68
+ # trim string to be no longer than max_length including zero byte
69
+ if max_length
70
+ max_length = 1 if max_length < 1
71
+ str = str[0, max_length]
72
+ if str.length == max_length and str[-1, 1] != "\0"
73
+ str[-1, 1] = "\0"
74
+ end
75
+ end
76
+
77
+ # ensure last byte in the string is a zero byte
78
+ str << "\0" if str[-1, 1] != "\0"
79
+
80
+ str
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,292 @@
1
+ require 'bindata/base'
2
+
3
+ module BinData
4
+ # A Struct is an ordered collection of named data objects.
5
+ #
6
+ # require 'bindata'
7
+ #
8
+ # class Tuple < BinData::Struct
9
+ # int8 :x
10
+ # int8 :y
11
+ # int8 :z
12
+ # end
13
+ #
14
+ # class SomeStruct < BinData::Struct
15
+ # hide 'a'
16
+ #
17
+ # int32le :a
18
+ # int16le :b
19
+ # tuple nil
20
+ # end
21
+ #
22
+ # obj = SomeStruct.new
23
+ # obj.field_names =># ["b", "x", "y", "z"]
24
+ #
25
+ # == Parameters
26
+ #
27
+ # Parameters may be provided at initialisation to control the behaviour of
28
+ # an object. These params are:
29
+ #
30
+ # <tt>:fields</tt>:: An array specifying the fields for this struct. Each
31
+ # element of the array is of the form
32
+ # [type, name, params]. Type is a symbol representing
33
+ # a registered type. Name is the name of this field.
34
+ # Name may be nil as in the example above. Params is an
35
+ # optional hash of parameters to pass to this field when
36
+ # instantiating it.
37
+ # <tt>:hide</tt>:: A list of the names of fields that are to be hidden
38
+ # from the outside world. Hidden fields don't appear
39
+ # in #snapshot or #field_names but are still accessible
40
+ # by name.
41
+ class Struct < Base
42
+ # A hash that can be accessed via attributes.
43
+ class Snapshot < Hash #:nodoc:
44
+ def method_missing(symbol, *args)
45
+ self[symbol.id2name] || super
46
+ end
47
+ end
48
+
49
+ # Register this class
50
+ register(self.name, self)
51
+
52
+ class << self
53
+ # Register the names of all subclasses of this class.
54
+ def inherited(subclass) #:nodoc:
55
+ register(subclass.name, subclass)
56
+ end
57
+
58
+ # Returns the names of any hidden fields in this struct. Any given args
59
+ # are appended to the hidden list.
60
+ def hide(*args)
61
+ # note that fields are stored in an instance variable not a class var
62
+ @hide ||= []
63
+ args.each do |name|
64
+ next if name.nil?
65
+ @hide << name.to_s
66
+ end
67
+ @hide
68
+ end
69
+
70
+ # Used to define fields for this structure.
71
+ def method_missing(symbol, *args)
72
+ name, params = args
73
+
74
+ type = symbol
75
+ name = name.to_s unless name.nil?
76
+ params ||= {}
77
+
78
+ if lookup(type).nil?
79
+ raise TypeError, "unknown type '#{type}' for #{self}", caller
80
+ end
81
+
82
+ # note that fields are stored in an instance variable not a class var
83
+
84
+ # check for duplicate names
85
+ @fields ||= []
86
+ if @fields.detect { |t, n, p| n == name and n != nil }
87
+ raise SyntaxError, "duplicate field '#{name}' in #{self}", caller
88
+ end
89
+
90
+ # remember this field. These fields will be recalled upon creating
91
+ # an instance of this class
92
+ @fields.push([type, name, params])
93
+ end
94
+
95
+ # Returns all stored fields. Should only be called by #cleaned_params.
96
+ def fields
97
+ @fields || []
98
+ end
99
+ end
100
+
101
+ # These are the parameters used by this class.
102
+ mandatory_parameter :fields
103
+ optional_parameter :hide
104
+
105
+ # Creates a new Struct.
106
+ def initialize(params = {}, env = nil)
107
+ super(cleaned_params(params), env)
108
+
109
+ # create instances of the fields
110
+ @fields = param(:fields).collect do |type, name, params|
111
+ klass = self.class.lookup(type)
112
+ raise TypeError, "unknown type '#{type}' for #{self}" if klass.nil?
113
+ [name, klass.new(params, create_env)]
114
+ end
115
+ end
116
+
117
+ # Clears the field represented by +name+. If no +name+
118
+ # is given, clears all fields in the struct.
119
+ def clear(name = nil)
120
+ if name.nil?
121
+ bindata_objects.each { |f| f.clear }
122
+ else
123
+ find_obj_for_name(name.to_s).clear
124
+ end
125
+ end
126
+
127
+ # Returns if the field represented by +name+ is clear?. If no +name+
128
+ # is given, returns whether all fields are clear.
129
+ def clear?(name = nil)
130
+ if name.nil?
131
+ bindata_objects.each { |f| return false if not f.clear? }
132
+ true
133
+ else
134
+ find_obj_for_name(name.to_s).clear?
135
+ end
136
+ end
137
+
138
+ # Reads the values for all fields in this object from +io+.
139
+ def _do_read(io)
140
+ bindata_objects.each { |f| f.do_read(io) }
141
+ end
142
+
143
+ # To be called after calling #read.
144
+ def done_read
145
+ bindata_objects.each { |f| f.done_read }
146
+ end
147
+
148
+ # Writes the values for all fields in this object to +io+.
149
+ def _write(io)
150
+ bindata_objects.each { |f| f.write(io) }
151
+ end
152
+
153
+ # Returns the number of bytes it will take to write the field represented
154
+ # by +name+. If +name+ is nil then returns the number of bytes required
155
+ # to write all fields.
156
+ def _num_bytes(name)
157
+ if name.nil?
158
+ bindata_objects.inject(0) { |sum, f| sum + f.num_bytes }
159
+ else
160
+ find_obj_for_name(name.to_s).num_bytes
161
+ end
162
+ end
163
+
164
+ # Returns a snapshot of this struct as a hash.
165
+ def snapshot
166
+ # allow structs to fake single value
167
+ return value if single_value?
168
+
169
+ hash = Snapshot.new
170
+ field_names.each do |name|
171
+ hash[name] = find_obj_for_name(name).snapshot
172
+ end
173
+ hash
174
+ end
175
+
176
+ # Returns a list of the names of all fields accessible through this
177
+ # object. +include_hidden+ specifies whether to include hidden names
178
+ # in the listing.
179
+ def field_names(include_hidden = false)
180
+ # single values don't have any fields
181
+ return [] if single_value?
182
+
183
+ names = []
184
+ @fields.each do |name, obj|
185
+ if name != ""
186
+ names << name unless (param(:hide).include?(name) and !include_hidden)
187
+ else
188
+ names.concat(obj.field_names)
189
+ end
190
+ end
191
+ names
192
+ end
193
+
194
+ # Returns the data object that stores values for +name+.
195
+ def find_obj_for_name(name)
196
+ @fields.each do |n, o|
197
+ if n == name
198
+ return o
199
+ elsif n == "" and o.field_names.include?(name)
200
+ return o.find_obj_for_name(name)
201
+ end
202
+ end
203
+ nil
204
+ end
205
+
206
+ def offset_of(field)
207
+ field_name = field.to_s
208
+ offset = 0
209
+ @fields.each do |name, obj|
210
+ if name != ""
211
+ break if name == field_name
212
+ offset += obj.num_bytes
213
+ elsif obj.field_names.include?(field_name)
214
+ offset += obj.offset_of(field)
215
+ break
216
+ end
217
+ end
218
+ offset
219
+ end
220
+
221
+ # Override to include field names.
222
+ alias_method :orig_respond_to?, :respond_to?
223
+ def respond_to?(symbol, include_private = false)
224
+ orig_respond_to?(symbol, include_private) ||
225
+ field_names(true).include?(symbol.id2name.chomp("="))
226
+ end
227
+
228
+ # Returns whether this data object contains a single value. Single
229
+ # value data objects respond to <tt>#value</tt> and <tt>#value=</tt>.
230
+ def single_value?
231
+ # need to use original respond_to? to prevent infinite recursion
232
+ orig_respond_to?(:value)
233
+ end
234
+
235
+ def method_missing(symbol, *args)
236
+ name = symbol.id2name
237
+
238
+ is_writer = (name[-1, 1] == "=")
239
+ name.chomp!("=")
240
+
241
+ # find the object that is responsible for name
242
+ if (obj = find_obj_for_name(name))
243
+ # pass on the request
244
+ if obj.single_value? and is_writer
245
+ obj.value = *args
246
+ elsif obj.single_value?
247
+ obj.value
248
+ else
249
+ obj
250
+ end
251
+ else
252
+ super
253
+ end
254
+ end
255
+
256
+ #---------------
257
+ private
258
+
259
+ # Returns a list of all the bindata objects for this struct.
260
+ def bindata_objects
261
+ @fields.collect { |f| f[1] }
262
+ end
263
+
264
+ # Returns a hash of cleaned +params+. Cleaning means that param
265
+ # values are converted to a desired format.
266
+ def cleaned_params(params)
267
+ new_params = params.dup
268
+
269
+ # use fields defined in this class if no fields are passed as params
270
+ fields = new_params[:fields] || self.class.fields
271
+
272
+ # ensure the names of fields are strings and that params is a hash
273
+ new_params[:fields] = fields.collect do |t, n, p|
274
+ [t, n.to_s, (p || {}).dup]
275
+ end
276
+
277
+ # collect all non blank field names
278
+ field_names = new_params[:fields].collect { |f| f[1] }
279
+ field_names = field_names.delete_if { |n| n == "" }
280
+
281
+ # collect all hidden names that correspond to a field name
282
+ hide = []
283
+ (new_params[:hide] || self.class.hide).each do |h|
284
+ h = h.to_s
285
+ hide << h if field_names.include?(h)
286
+ end
287
+ new_params[:hide] = hide
288
+
289
+ new_params
290
+ end
291
+ end
292
+ end