bindata 1.6.0 → 1.8.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.

@@ -3,11 +3,7 @@ require 'stringio'
3
3
  module BinData
4
4
  # A wrapper around an IO object. The wrapper provides a consistent
5
5
  # interface for BinData objects to use when accessing the IO.
6
- class IO
7
-
8
- # The underlying IO is unseekable
9
- class Unseekable < StandardError; end
10
-
6
+ module IO
11
7
  # Creates a StringIO around +str+.
12
8
  def self.create_string_io(str = "")
13
9
  if str.respond_to?(:force_encoding)
@@ -16,10 +12,10 @@ module BinData
16
12
  StringIO.new(str)
17
13
  end
18
14
 
19
- # Create a new IO wrapper around +io+. +io+ must support #read if used
20
- # for reading, #write if used for writing, #pos if reading the current
21
- # stream position and #seek if setting the current stream position. If
22
- # +io+ is a string it will be automatically wrapped in an StringIO object.
15
+ # Create a new IO Read wrapper around +io+. +io+ must provide #read,
16
+ # #pos if reading the current stream position and #seek if setting the
17
+ # current stream position. If +io+ is a string it will be automatically
18
+ # wrapped in an StringIO object.
23
19
  #
24
20
  # The IO can handle bitstreams in either big or little endian format.
25
21
  #
@@ -33,242 +29,377 @@ module BinData
33
29
  # In little endian format:
34
30
  # readbits(6), readbits(5) #=> [543210, a9876]
35
31
  #
36
- def initialize(io)
37
- raise ArgumentError, "io must not be a BinData::IO" if BinData::IO === io
32
+ class Read
33
+ def initialize(io)
34
+ raise ArgumentError, "io must not be a BinData::IO::Read" if BinData::IO::Read === io
38
35
 
39
- # wrap strings in a StringIO
40
- if io.respond_to?(:to_str)
41
- io = BinData::IO.create_string_io(io.to_str)
42
- end
36
+ # wrap strings in a StringIO
37
+ if io.respond_to?(:to_str)
38
+ io = BinData::IO.create_string_io(io.to_str)
39
+ end
43
40
 
44
- @raw_io = io
41
+ @raw_io = io
45
42
 
46
- # initial stream position if stream supports positioning
47
- @initial_pos = current_position rescue 0
43
+ # bits when reading
44
+ @rnbits = 0
45
+ @rval = 0
46
+ @rendian = nil
48
47
 
49
- # bits when reading
50
- @rnbits = 0
51
- @rval = 0
52
- @rendian = nil
48
+ @buffer_end_pos = nil
53
49
 
54
- # bits when writing
55
- @wnbits = 0
56
- @wval = 0
57
- @wendian = nil
58
- end
50
+ extend seekable? ? SeekableStream : UnSeekableStream
51
+ end
59
52
 
60
- # Access to the underlying raw io.
61
- attr_reader :raw_io
53
+ # Sets a buffer of +n+ bytes on the io stream. Any reading or seeking
54
+ # calls inside the +block+ will be contained within this buffer.
55
+ def with_buffer(n, &block)
56
+ prev = @buffer_end_pos
57
+ if prev
58
+ avail = prev - offset
59
+ n = avail if n > avail
60
+ end
61
+ @buffer_end_pos = offset + n
62
+ begin
63
+ block.call
64
+ read
65
+ ensure
66
+ @buffer_end_pos = prev
67
+ end
68
+ end
62
69
 
63
- # Returns the current offset of the io stream. The exact value of
64
- # the offset when reading bitfields is not defined.
65
- def offset
66
- current_position - @initial_pos
67
- rescue Unseekable
68
- 0
69
- end
70
+ # Seek +n+ bytes from the current position in the io stream.
71
+ def seekbytes(n)
72
+ reset_read_bits
73
+ seek(n)
74
+ end
70
75
 
71
- # The number of bytes remaining in the input stream.
72
- def num_bytes_remaining
73
- pos = current_position
74
- @raw_io.seek(0, ::IO::SEEK_END)
75
- bytes_remaining = current_position - pos
76
- @raw_io.seek(pos, ::IO::SEEK_SET)
76
+ # Reads exactly +n+ bytes from +io+.
77
+ #
78
+ # If the data read is nil an EOFError is raised.
79
+ #
80
+ # If the data read is too short an IOError is raised.
81
+ def readbytes(n)
82
+ reset_read_bits
77
83
 
78
- bytes_remaining
79
- rescue Unseekable
80
- 0
81
- end
84
+ str = read(n)
85
+ raise EOFError, "End of file reached" if str.nil?
86
+ raise IOError, "data truncated" if str.size < n
87
+ str
88
+ end
82
89
 
83
- # Seek +n+ bytes from the current position in the io stream.
84
- def seekbytes(n)
85
- reset_read_bits
86
- @raw_io.seek(n, ::IO::SEEK_CUR)
87
- rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE
88
- skipbytes(n)
89
- end
90
+ # Reads all remaining bytes from the stream.
91
+ def read_all_bytes
92
+ reset_read_bits
93
+ read
94
+ end
90
95
 
91
- # Reads exactly +n+ bytes from +io+.
92
- #
93
- # If the data read is nil an EOFError is raised.
94
- #
95
- # If the data read is too short an IOError is raised.
96
- def readbytes(n)
97
- reset_read_bits
98
-
99
- str = @raw_io.read(n)
100
- raise EOFError, "End of file reached" if str.nil?
101
- raise IOError, "data truncated" if str.size < n
102
- str
103
- end
96
+ # Reads exactly +nbits+ bits from the stream. +endian+ specifies whether
97
+ # the bits are stored in +:big+ or +:little+ endian format.
98
+ def readbits(nbits, endian)
99
+ if @rendian != endian
100
+ # don't mix bits of differing endian
101
+ reset_read_bits
102
+ @rendian = endian
103
+ end
104
104
 
105
- # Reads all remaining bytes from the stream.
106
- def read_all_bytes
107
- reset_read_bits
108
- @raw_io.read
109
- end
105
+ if endian == :big
106
+ read_big_endian_bits(nbits)
107
+ else
108
+ read_little_endian_bits(nbits)
109
+ end
110
+ end
110
111
 
111
- # Reads exactly +nbits+ bits from the stream. +endian+ specifies whether
112
- # the bits are stored in +:big+ or +:little+ endian format.
113
- def readbits(nbits, endian)
114
- if @rendian != endian
115
- # don't mix bits of differing endian
116
- reset_read_bits
117
- @rendian = endian
112
+ # Discards any read bits so the stream becomes aligned at the
113
+ # next byte boundary.
114
+ def reset_read_bits
115
+ @rnbits = 0
116
+ @rval = 0
118
117
  end
119
118
 
120
- if endian == :big
121
- read_big_endian_bits(nbits)
122
- else
123
- read_little_endian_bits(nbits)
119
+ #---------------
120
+ private
121
+
122
+ def seekable?
123
+ @raw_io.pos
124
+ rescue NoMethodError, Errno::ESPIPE, Errno::EPIPE
125
+ nil
124
126
  end
125
- end
126
127
 
127
- # Discards any read bits so the stream becomes aligned at the
128
- # next byte boundary.
129
- def reset_read_bits
130
- @rnbits = 0
131
- @rval = 0
132
- end
128
+ def seek(n)
129
+ seek_raw(buffer_limited_n(n))
130
+ end
133
131
 
134
- # Writes the given string of bytes to the io stream.
135
- def writebytes(str)
136
- flushbits
137
- @raw_io.write(str)
138
- end
132
+ def read(n = nil)
133
+ read_raw(buffer_limited_n(n))
134
+ end
139
135
 
140
- # Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether
141
- # the bits are to be stored in +:big+ or +:little+ endian format.
142
- def writebits(val, nbits, endian)
143
- if @wendian != endian
144
- # don't mix bits of differing endian
145
- flushbits
146
- @wendian = endian
136
+ def buffer_limited_n(n)
137
+ if @buffer_end_pos
138
+ max = @buffer_end_pos - offset
139
+ n = max if n.nil? or n > max
140
+ end
141
+
142
+ n
147
143
  end
148
144
 
149
- clamped_val = val & mask(nbits)
145
+ def read_big_endian_bits(nbits)
146
+ while @rnbits < nbits
147
+ accumulate_big_endian_bits
148
+ end
149
+
150
+ val = (@rval >> (@rnbits - nbits)) & mask(nbits)
151
+ @rnbits -= nbits
152
+ @rval &= mask(@rnbits)
150
153
 
151
- if endian == :big
152
- write_big_endian_bits(clamped_val, nbits)
153
- else
154
- write_little_endian_bits(clamped_val, nbits)
154
+ val
155
155
  end
156
- end
157
156
 
158
- # To be called after all +writebits+ have been applied.
159
- def flushbits
160
- raise "Internal state error nbits = #{@wnbits}" if @wnbits >= 8
157
+ def accumulate_big_endian_bits
158
+ byte = read(1)
159
+ raise EOFError, "End of file reached" if byte.nil?
160
+ byte = byte.unpack('C').at(0) & 0xff
161
161
 
162
- if @wnbits > 0
163
- writebits(0, 8 - @wnbits, @wendian)
162
+ @rval = (@rval << 8) | byte
163
+ @rnbits += 8
164
164
  end
165
- end
166
- alias_method :flush, :flushbits
167
165
 
168
- #---------------
169
- private
166
+ def read_little_endian_bits(nbits)
167
+ while @rnbits < nbits
168
+ accumulate_little_endian_bits
169
+ end
170
170
 
171
- def current_position
172
- @raw_io.pos
173
- rescue NoMethodError, Errno::ESPIPE
174
- raise Unseekable
175
- end
171
+ val = @rval & mask(nbits)
172
+ @rnbits -= nbits
173
+ @rval >>= nbits
176
174
 
177
- def skipbytes(n)
178
- # skip over data in 8k blocks
179
- while n > 0
180
- bytes_to_read = [n, 8192].min
181
- @raw_io.read(bytes_to_read)
182
- n -= bytes_to_read
175
+ val
183
176
  end
184
- end
185
177
 
186
- def read_big_endian_bits(nbits)
187
- while @rnbits < nbits
188
- accumulate_big_endian_bits
178
+ def accumulate_little_endian_bits
179
+ byte = read(1)
180
+ raise EOFError, "End of file reached" if byte.nil?
181
+ byte = byte.unpack('C').at(0) & 0xff
182
+
183
+ @rval = @rval | (byte << @rnbits)
184
+ @rnbits += 8
189
185
  end
190
186
 
191
- val = (@rval >> (@rnbits - nbits)) & mask(nbits)
192
- @rnbits -= nbits
193
- @rval &= mask(@rnbits)
187
+ def mask(nbits)
188
+ (1 << nbits) - 1
189
+ end
194
190
 
195
- val
196
- end
191
+ # Use #seek and #pos on seekable streams
192
+ module SeekableStream
193
+ # Returns the current offset of the io stream. The exact value of
194
+ # the offset when reading bitfields is not defined.
195
+ def offset
196
+ raw_io.pos - @initial_pos
197
+ end
197
198
 
198
- def accumulate_big_endian_bits
199
- byte = @raw_io.read(1)
200
- raise EOFError, "End of file reached" if byte.nil?
201
- byte = byte.unpack('C').at(0) & 0xff
199
+ # The number of bytes remaining in the input stream.
200
+ def num_bytes_remaining
201
+ mark = raw_io.pos
202
+ raw_io.seek(0, ::IO::SEEK_END)
203
+ bytes_remaining = raw_io.pos - mark
204
+ raw_io.seek(mark, ::IO::SEEK_SET)
202
205
 
203
- @rval = (@rval << 8) | byte
204
- @rnbits += 8
205
- end
206
+ bytes_remaining
207
+ end
208
+
209
+ #-----------
210
+ private
211
+
212
+ def read_raw(n)
213
+ raw_io.read(n)
214
+ end
215
+
216
+ def seek_raw(n)
217
+ raw_io.seek(n, ::IO::SEEK_CUR)
218
+ end
206
219
 
207
- def read_little_endian_bits(nbits)
208
- while @rnbits < nbits
209
- accumulate_little_endian_bits
220
+ def raw_io
221
+ @initial_pos ||= @raw_io.pos
222
+ @raw_io
223
+ end
210
224
  end
211
225
 
212
- val = @rval & mask(nbits)
213
- @rnbits -= nbits
214
- @rval >>= nbits
226
+ # Manually keep track of offset for unseekable streams.
227
+ module UnSeekableStream
228
+ # Returns the current offset of the io stream. The exact value of
229
+ # the offset when reading bitfields is not defined.
230
+ def offset
231
+ @read_count ||= 0
232
+ end
215
233
 
216
- val
217
- end
234
+ # The number of bytes remaining in the input stream.
235
+ def num_bytes_remaining
236
+ raise IOError, "stream is unseekable"
237
+ end
218
238
 
219
- def accumulate_little_endian_bits
220
- byte = @raw_io.read(1)
221
- raise EOFError, "End of file reached" if byte.nil?
222
- byte = byte.unpack('C').at(0) & 0xff
239
+ #-----------
240
+ private
223
241
 
224
- @rval = @rval | (byte << @rnbits)
225
- @rnbits += 8
226
- end
242
+ def read_raw(n)
243
+ @read_count ||= 0
227
244
 
228
- def write_big_endian_bits(val, nbits)
229
- while nbits > 0
230
- bits_req = 8 - @wnbits
231
- if nbits >= bits_req
232
- msb_bits = (val >> (nbits - bits_req)) & mask(bits_req)
233
- nbits -= bits_req
234
- val &= mask(nbits)
245
+ data = @raw_io.read(n)
246
+ @read_count += data.size if data
247
+ data
248
+ end
235
249
 
236
- @wval = (@wval << bits_req) | msb_bits
237
- @raw_io.write(@wval.chr)
250
+ def seek_raw(n)
251
+ raise IOError, "stream is unseekable" if n < 0
238
252
 
239
- @wval = 0
240
- @wnbits = 0
241
- else
242
- @wval = (@wval << nbits) | val
243
- @wnbits += nbits
244
- nbits = 0
253
+ # skip over data in 8k blocks
254
+ while n > 0
255
+ bytes_to_read = [n, 8192].min
256
+ read_raw(bytes_to_read)
257
+ n -= bytes_to_read
258
+ end
245
259
  end
246
260
  end
247
261
  end
248
262
 
249
- def write_little_endian_bits(val, nbits)
250
- while nbits > 0
251
- bits_req = 8 - @wnbits
252
- if nbits >= bits_req
253
- lsb_bits = val & mask(bits_req)
254
- nbits -= bits_req
255
- val >>= bits_req
263
+ # Create a new IO Write wrapper around +io+. +io+ must provide #write.
264
+ # If +io+ is a string it will be automatically wrapped in an StringIO
265
+ # object.
266
+ #
267
+ # The IO can handle bitstreams in either big or little endian format.
268
+ #
269
+ # See IO::Read for more information.
270
+ class Write
271
+ def initialize(io)
272
+ if BinData::IO::Write === io
273
+ raise ArgumentError, "io must not be a BinData::IO::Write"
274
+ end
275
+
276
+ # wrap strings in a StringIO
277
+ if io.respond_to?(:to_str)
278
+ io = BinData::IO.create_string_io(io.to_str)
279
+ end
280
+
281
+ @raw_io = io
282
+
283
+ @wnbits = 0
284
+ @wval = 0
285
+ @wendian = nil
286
+
287
+ @bytes_remaining = nil
288
+ end
289
+
290
+ # Sets a buffer of +n+ bytes on the io stream. Any writes inside the
291
+ # +block+ will be contained within this buffer. If less than +n+ bytes
292
+ # are written inside the block, the remainder will be padded with '\0'
293
+ # bytes.
294
+ def with_buffer(n, &block)
295
+ prev = @bytes_remaining
296
+ if prev
297
+ n = prev if n > prev
298
+ prev -= n
299
+ end
300
+
301
+ @bytes_remaining = n
302
+ begin
303
+ block.call
304
+ write_raw("\0" * @bytes_remaining)
305
+ ensure
306
+ @bytes_remaining = prev
307
+ end
308
+ end
309
+
310
+ # Writes the given string of bytes to the io stream.
311
+ def writebytes(str)
312
+ flushbits
313
+ write_raw(str)
314
+ end
315
+
316
+ # Writes +nbits+ bits from +val+ to the stream. +endian+ specifies whether
317
+ # the bits are to be stored in +:big+ or +:little+ endian format.
318
+ def writebits(val, nbits, endian)
319
+ if @wendian != endian
320
+ # don't mix bits of differing endian
321
+ flushbits
322
+ @wendian = endian
323
+ end
256
324
 
257
- @wval = @wval | (lsb_bits << @wnbits)
258
- @raw_io.write(@wval.chr)
325
+ clamped_val = val & mask(nbits)
259
326
 
260
- @wval = 0
261
- @wnbits = 0
327
+ if endian == :big
328
+ write_big_endian_bits(clamped_val, nbits)
262
329
  else
263
- @wval = @wval | (val << @wnbits)
264
- @wnbits += nbits
265
- nbits = 0
330
+ write_little_endian_bits(clamped_val, nbits)
266
331
  end
267
332
  end
268
- end
269
333
 
270
- def mask(nbits)
271
- (1 << nbits) - 1
334
+ # To be called after all +writebits+ have been applied.
335
+ def flushbits
336
+ raise "Internal state error nbits = #{@wnbits}" if @wnbits >= 8
337
+
338
+ if @wnbits > 0
339
+ writebits(0, 8 - @wnbits, @wendian)
340
+ end
341
+ end
342
+ alias_method :flush, :flushbits
343
+
344
+ #---------------
345
+ private
346
+
347
+ def write_big_endian_bits(val, nbits)
348
+ while nbits > 0
349
+ bits_req = 8 - @wnbits
350
+ if nbits >= bits_req
351
+ msb_bits = (val >> (nbits - bits_req)) & mask(bits_req)
352
+ nbits -= bits_req
353
+ val &= mask(nbits)
354
+
355
+ @wval = (@wval << bits_req) | msb_bits
356
+ write_raw(@wval.chr)
357
+
358
+ @wval = 0
359
+ @wnbits = 0
360
+ else
361
+ @wval = (@wval << nbits) | val
362
+ @wnbits += nbits
363
+ nbits = 0
364
+ end
365
+ end
366
+ end
367
+
368
+ def write_little_endian_bits(val, nbits)
369
+ while nbits > 0
370
+ bits_req = 8 - @wnbits
371
+ if nbits >= bits_req
372
+ lsb_bits = val & mask(bits_req)
373
+ nbits -= bits_req
374
+ val >>= bits_req
375
+
376
+ @wval = @wval | (lsb_bits << @wnbits)
377
+ write_raw(@wval.chr)
378
+
379
+ @wval = 0
380
+ @wnbits = 0
381
+ else
382
+ @wval = @wval | (val << @wnbits)
383
+ @wnbits += nbits
384
+ nbits = 0
385
+ end
386
+ end
387
+ end
388
+
389
+ def write_raw(data)
390
+ if @bytes_remaining
391
+ if data.size > @bytes_remaining
392
+ data = data[0, @bytes_remaining]
393
+ end
394
+ @bytes_remaining -= data.size
395
+ end
396
+
397
+ @raw_io.write(data)
398
+ end
399
+
400
+ def mask(nbits)
401
+ (1 << nbits) - 1
402
+ end
272
403
  end
273
404
  end
274
405
  end