binaryparse 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/binaryparse.gemspec +18 -0
  2. data/doc/classes/BinaryBlocker/BitFieldEncoder.html +328 -0
  3. data/doc/classes/BinaryBlocker/Blocker.html +168 -0
  4. data/doc/classes/BinaryBlocker/CountedArrayEncoder.html +347 -0
  5. data/doc/classes/BinaryBlocker/Encoder.html +526 -0
  6. data/doc/classes/BinaryBlocker/FixedArrayEncoder.html +278 -0
  7. data/doc/classes/BinaryBlocker/FixedStringEncoder.html +177 -0
  8. data/doc/classes/BinaryBlocker/FixedUTF16StringEncoder.html +229 -0
  9. data/doc/classes/BinaryBlocker/GroupEncoder.html +650 -0
  10. data/doc/classes/BinaryBlocker/ListOfEncoder.html +343 -0
  11. data/doc/classes/BinaryBlocker/OneOfEncoder.html +306 -0
  12. data/doc/classes/BinaryBlocker/PackedDateEncoder.html +220 -0
  13. data/doc/classes/BinaryBlocker/PackedDateTimeEncoder.html +220 -0
  14. data/doc/classes/BinaryBlocker/PackedNumberEncoder.html +231 -0
  15. data/doc/classes/BinaryBlocker/SimpleEncoder.html +284 -0
  16. data/doc/classes/BinaryBlocker.html +329 -0
  17. data/doc/classes/BufferedIO.html +324 -0
  18. data/doc/classes/TestBlocker/BBDate.html +120 -0
  19. data/doc/classes/TestBlocker/BBList.html +120 -0
  20. data/doc/classes/TestBlocker/BBPacked.html +120 -0
  21. data/doc/classes/TestBlocker/BBString.html +120 -0
  22. data/doc/classes/TestBlocker/BBSub1.html +120 -0
  23. data/doc/classes/TestBlocker/BBSub2.html +120 -0
  24. data/doc/classes/TestBlocker/BBTest1.html +120 -0
  25. data/doc/classes/TestBlocker/BBTest2.html +120 -0
  26. data/doc/classes/TestBlocker/BBTest3.html +120 -0
  27. data/doc/classes/TestBlocker/BBTest4.html +120 -0
  28. data/doc/classes/TestBlocker/BBTest5.html +120 -0
  29. data/doc/classes/TestBlocker/BBTest6.html +120 -0
  30. data/doc/classes/TestBlocker/BBTest7.html +120 -0
  31. data/doc/classes/TestBlocker/BBTime.html +120 -0
  32. data/doc/classes/TestBlocker/BBUTF16.html +120 -0
  33. data/doc/classes/TestBlocker/ItemA.html +120 -0
  34. data/doc/classes/TestBlocker/ItemB.html +120 -0
  35. data/doc/classes/TestBlocker.html +802 -0
  36. data/doc/created.rid +1 -0
  37. data/doc/dot/f_0.dot +208 -0
  38. data/doc/dot/f_0.png +0 -0
  39. data/doc/dot/f_1.dot +23 -0
  40. data/doc/dot/f_1.png +0 -0
  41. data/doc/dot/f_2.dot +32 -0
  42. data/doc/dot/f_2.png +0 -0
  43. data/doc/dot/m_0_0.dot +208 -0
  44. data/doc/dot/m_0_0.png +0 -0
  45. data/doc/files/lib/blocker_rb.html +144 -0
  46. data/doc/files/lib/buffered_io_rb.html +115 -0
  47. data/doc/files/test/test_blocker_rb.html +116 -0
  48. data/doc/fr_class_index.html +60 -0
  49. data/doc/fr_file_index.html +29 -0
  50. data/doc/fr_method_index.html +135 -0
  51. data/doc/index.html +24 -0
  52. data/doc/rdoc-style.css +208 -0
  53. data/lib/blocker.rb +736 -0
  54. data/lib/buffered_io.rb +58 -0
  55. data/test/test_blocker.rb +412 -0
  56. metadata +111 -0
data/lib/blocker.rb ADDED
@@ -0,0 +1,736 @@
1
+ # The purpose of BinaryBlocker is to parse semi-complicated binary files. It
2
+ # can handle all the various data types supported by Array#pack and
3
+ # String#unpack, but the real benefit is with more complicated layouts, that
4
+ # might include files with headers and a variable number of body records
5
+ # followed by a footer. It also supports, packed numeric
6
+ # (BinaryBlocker::PackedNumberEncoder) and date fields
7
+ # (BinaryBlocker::PackedDateEncoder)
8
+
9
+ require 'uconv'
10
+ require 'date'
11
+ require 'time'
12
+
13
+ module BinaryBlocker
14
+ class << self
15
+ def klasses
16
+ @klasses ||= {}
17
+ end
18
+
19
+ # To simplify naming of classes and laying out fields in your structures
20
+ # they can be registered:
21
+ #
22
+ # BinaryBlocker.register_klass(:string, FixedStringEncoder)
23
+ def register_klass(sym, klass)
24
+ @klasses ||= {}
25
+ @klasses[sym] = klass
26
+ end
27
+
28
+ # Handy helper method that returns the size of a given pack/unpack format
29
+ # string
30
+ def sizeof_format(format)
31
+ length = 0
32
+ format.scan(/(\S_?)\s*(\d*)/).each do |directive,count|
33
+ count = count.to_i
34
+ count = 1 if count == 0
35
+
36
+ length += case directive
37
+ when 'A', 'a', 'C', 'c', 'Z', 'x' : count
38
+ when 'B', 'b' : (count / 8.0).ceil
39
+ when 'D', 'd', 'E', 'G' : count * 8
40
+ when 'e', 'F', 'f', 'g' : count * 4
41
+ when 'H', 'h' : (count / 2.0).ceil
42
+ when 'I', 'i', 'L', 'l', 'N', 'V' : count * 4
43
+ when 'n', 'S', 's', 'v' : count * 2
44
+ when 'Q', 'q' : count * 8
45
+ when 'X' : count * -1
46
+ else raise ArgumentError.new("#{directive} is not supported in sizeof_format")
47
+ end
48
+ end
49
+
50
+ length
51
+ end
52
+
53
+ def pack_symbols
54
+ {
55
+ :int8 => 'c',
56
+ :uint8 => 'C',
57
+ :int16 => 's',
58
+ :uint16 => 'S',
59
+ :int32 => 'i',
60
+ :uint32 => 'I',
61
+ :int64 => 'q',
62
+ :uint64 => 'Q'
63
+ }
64
+ end
65
+
66
+ # As we have to process some fields to determine what type they are
67
+ # (that is we sometimes start and half to backup), this routine takes
68
+ # any io (that can be repositioned) and yields to a block -- if the
69
+ # block returns not _true_ it will reset the original position of the
70
+ # io stream. If you need to use BinaryBlocker with a non-repositioning
71
+ # stream (like a TCP/IP stream), see the handy BufferedIO class.
72
+ def with_guarded_io_pos(io)
73
+ pos = io.pos
74
+ status = yield io
75
+ ensure
76
+ io.pos = pos unless status
77
+ end
78
+
79
+ end
80
+
81
+ # This is the base class for all the various encoders. It supports a variety
82
+ # of options (as Symbols in a Hash):
83
+ #
84
+ # pre_block:: passed the value before being blocked
85
+ # post_block:: passed the blocked value before returned
86
+ # pre_deblock:: passed the io before attempting to deblock it (hard to imagine
87
+ # why you would need this but I was compelled by orthaganality)
88
+ # post_deblock:: passed the deblocked value before being stored internally
89
+ # get_filter:: more info
90
+ # set_filter:: all done
91
+ #
92
+ # It also supports either a string or io parameter which will be used to
93
+ # initialize the class
94
+ class Encoder
95
+
96
+ # Parameters: (io | buf, options_hash)
97
+ #
98
+ # Options (lambda):
99
+ #
100
+ # pre_block:: passed the value before being blocked
101
+ # post_block:: passed the blocked value before returned
102
+ # pre_deblock:: passed the io before attempting to deblock it (hard to imagine
103
+ # why you would need this but I was compelled by orthaganality)
104
+ # post_deblock:: passed the deblocked value before being stored internally
105
+ # get_filter:: more info
106
+ # set_filter:: all done
107
+ #
108
+ def initialize(*opts)
109
+ initialize_options(*opts)
110
+ initialize_data(*opts)
111
+ end
112
+
113
+ #
114
+ def block(io=nil)
115
+ val = if @pre_block
116
+ @pre_block.call(self.value)
117
+ else
118
+ self.value
119
+ end
120
+ result = internal_block(val)
121
+ if @post_block
122
+ result = @post_block.call(result)
123
+ end
124
+ io.write(result) if io
125
+ result
126
+ end
127
+
128
+ # This routine takes an io and will parse the stream
129
+ # on success it returns the object, on failure it
130
+ # returns a nil
131
+ def deblock(io)
132
+ with_guarded_io_pos(io) do
133
+ if @pre_deblock
134
+ # does this serve any real purpose? other
135
+ # than making me feel good and orthoginal
136
+ io = @pre_deblock.call(io)
137
+ end
138
+ self.value = internal_deblock(io)
139
+ if @post_deblock
140
+ self.value = @post_deblock.call(self.value)
141
+ end
142
+ self.value
143
+ end
144
+ end
145
+
146
+ protected
147
+
148
+ # Override valid? to allow check constraints on a particular
149
+ # record type, on failure
150
+ def valid?
151
+ true
152
+ end
153
+
154
+ def value
155
+ v = @value
156
+ if @get_filter
157
+ @get_filter.call(v)
158
+ else
159
+ v
160
+ end
161
+ end
162
+
163
+ def value=(val)
164
+ val = @set_filter.call(val) if @set_filter
165
+ @value = val
166
+ end
167
+
168
+ def initialize_options(*opts)
169
+ @opts ||= {}
170
+ opts = opts.find { |o| o.respond_to? :to_hash }
171
+ if opts
172
+ @opts = @opts.merge(opts)
173
+ @pre_block = @opts[:pre_block]
174
+ @pre_deblock = @opts[:pre_deblock]
175
+
176
+ @get_filter = @opts[:get_filter]
177
+ @set_filter = @opts[:set_filter]
178
+
179
+ @post_block = @opts[:post_block]
180
+ @post_deblock = @opts[:post_deblock]
181
+ end
182
+ end
183
+
184
+ def initialize_data(*opts)
185
+ data = opts.find { |o| !o.respond_to? :to_hash }
186
+ if data.respond_to? :to_str
187
+ raise "Unable to parse block" unless deblock(StringIO.new(data))
188
+ elsif data.respond_to? :read
189
+ raise "Unable to parse block" unless deblock(data)
190
+ end
191
+ end
192
+
193
+ def with_guarded_io_pos(io)
194
+ result = nil
195
+ BinaryBlocker.with_guarded_io_pos(io) do
196
+ result = yield io
197
+ valid?
198
+ end && result
199
+ end
200
+
201
+ def internal_block(value)
202
+ value
203
+ end
204
+
205
+ def internal_deblock(io)
206
+ ''
207
+ end
208
+ end
209
+
210
+ # All Encoders that store multiple items subclass from
211
+ # here.
212
+ class GroupEncoder < Encoder
213
+ class << self
214
+ def inherited(obj)
215
+ obj.instance_eval do
216
+ @klasses = BinaryBlocker.klasses.clone
217
+ @attributes = []
218
+ @lookup = {}
219
+ end
220
+ super
221
+ end
222
+
223
+ # One and only one (this is the easiest :-)
224
+ def has_one(sym, klass, *opts)
225
+ klass = @klasses[klass] if @klasses[klass]
226
+ @lookup[sym] = @attributes.size
227
+ @attributes << lambda { klass.new(*opts) }
228
+ end
229
+
230
+ def include_klasses(klasses, *opts)
231
+ klasses = klasses.map do |k|
232
+ case
233
+ when @klasses[k] : lambda { @klasses[k].new(*opts) }
234
+ when k.respond_to?(:call) : k
235
+ when k.respond_to?(:new) : lambda { k.new(*opts) }
236
+ else raise "Unable to process class: #{k}"
237
+ end
238
+ end
239
+ end
240
+
241
+ def has_one_of(sym, klasses, *opts)
242
+ klasses = include_klasses(klasses, *opts)
243
+ @lookup[sym] = @attributes.size
244
+ @attributes << lambda { OneOfEncoder.new(klasses, *opts) }
245
+ end
246
+
247
+ def has_counted_array(sym, count_type, klasses, *opts)
248
+ klasses = include_klasses(klasses, *opts)
249
+ @lookup[sym] = @attributes.size
250
+ @attributes << lambda { CountedArrayEncoder.new(count_type, klasses, *opts) }
251
+ end
252
+
253
+ def has_fixed_array(sym, count, klasses, *opts)
254
+ klasses = include_klasses(klasses, *opts)
255
+ @lookup[sym] = @attributes.size
256
+ @attributes << lambda { FixedArrayEncoder.new(count, klasses, *opts) }
257
+ end
258
+
259
+ def has_bit_field(sym, type, bit_info, *opts)
260
+ @lookup[sym] = @attributes.size
261
+ @attributes << lambda { BitFieldEncoder.new(type, bit_info, *opts) }
262
+ end
263
+
264
+ def has_list_of(sym, klasses, *opts)
265
+ klasses = include_klasses(klasses, *opts)
266
+ @lookup[sym] = @attributes.size
267
+ @attributes << lambda { ListOfEncoder.new(klasses, *opts) }
268
+ end
269
+
270
+ def register_klass(sym, klass)
271
+ @klasses ||= {}
272
+ @klasses[sym] = klass
273
+ end
274
+
275
+ def clear_registered_klasses
276
+ @klasses = {}
277
+ end
278
+
279
+ def attributes
280
+ @attributes
281
+ end
282
+
283
+ def lookup
284
+ @lookup
285
+ end
286
+ end
287
+
288
+ def initialize(*opts)
289
+ @lookup = self.class.lookup.clone
290
+ @value = self.class.attributes.map { |a| a.call }
291
+ super
292
+ end
293
+
294
+ def block
295
+ @value.inject("") { |a,b| a + b.block }
296
+ end
297
+
298
+ def deblock(io)
299
+ BinaryBlocker.with_guarded_io_pos(io) do
300
+ @value.all? { |o| o.deblock(io) }
301
+ end
302
+ end
303
+
304
+ def method_missing(sym, *args)
305
+ super unless @lookup
306
+ if pos = @lookup[sym]
307
+ return @value[pos].value
308
+ else
309
+ sym = sym.to_s
310
+ if sym[-1] == ?=
311
+ if pos = @lookup[sym[0..-2].to_sym]
312
+ return @value[pos].value = args.first
313
+ end
314
+ end
315
+ end
316
+ super
317
+ end
318
+
319
+ def value
320
+ self
321
+ end
322
+
323
+ def value=(val)
324
+ raise "Now what?"
325
+ end
326
+
327
+ def valid?
328
+ @value.all? { |a| a.valid? }
329
+ end
330
+ end
331
+
332
+ class SimpleEncoder < Encoder
333
+
334
+ def self.register(sym, fmt, *opts)
335
+ klass = Class.new(SimpleEncoder)
336
+ klass.send(:define_method,:initialize) do |*opts|
337
+ initialize_options(*opts)
338
+
339
+ @format = fmt
340
+ @length = BinaryBlocker.sizeof_format(@format)
341
+
342
+ @key = @opts[:key]
343
+ @valid = @opts[:valid]
344
+
345
+ initialize_data(*opts)
346
+ end
347
+ BinaryBlocker.register_klass(sym, klass)
348
+ end
349
+
350
+ def internal_block(val)
351
+ [val].pack(@format)
352
+ end
353
+
354
+ def internal_deblock(io)
355
+ buffer = io.read(@length)
356
+ result = buffer.unpack(@format)
357
+ result.first
358
+ end
359
+
360
+ def valid?
361
+ if @valid
362
+ @valid.call(self.value)
363
+ else
364
+ self.value != nil && (@key == nil || @key === self.value)
365
+ end
366
+ end
367
+
368
+ def inspect
369
+ "SE: #{self.class} - #{@format} - #{@length} - #{self.value.inspect}"
370
+ end
371
+ end
372
+
373
+ SimpleEncoder.register( :int8, 'c')
374
+ SimpleEncoder.register(:uint8, 'C')
375
+ SimpleEncoder.register( :int16, 's')
376
+ SimpleEncoder.register(:uint16, 'S')
377
+ SimpleEncoder.register( :int32, 'i')
378
+ SimpleEncoder.register(:uint32, 'I')
379
+ SimpleEncoder.register( :int64, 'q')
380
+ SimpleEncoder.register(:uint64, 'Q')
381
+
382
+ class FixedStringEncoder < SimpleEncoder
383
+ def initialize(*opts)
384
+ initialize_options(*opts)
385
+
386
+ @length = @opts[:length].to_i
387
+ raise ArgumentError.new("Missing or invalid string length") unless @length > 0
388
+ @format = "Z#{@length}"
389
+
390
+ @key = @opts[:key]
391
+ @valid = @opts[:valid]
392
+
393
+ initialize_data(*opts)
394
+ end
395
+ end
396
+ BinaryBlocker.register_klass(:string, FixedStringEncoder)
397
+
398
+ class FixedUTF16StringEncoder < SimpleEncoder
399
+ def initialize(*opts)
400
+ initialize_options(*opts)
401
+
402
+ @length = @opts[:length].to_i
403
+ @length *= 2
404
+ raise ArgumentError.new("Missing or invalid string length") unless @length > 0
405
+ @format = "Z#{@length}"
406
+
407
+ @key = @opts[:key]
408
+ @valid = @opts[:valid]
409
+
410
+ initialize_data(*opts)
411
+ end
412
+
413
+ def internal_block(val)
414
+ [Uconv.u8tou16(val || "")].pack(@format)
415
+ end
416
+
417
+ def internal_deblock(io)
418
+ buffer = io.read(@length)
419
+ Uconv.u16tou8(buffer).sub(/\000+$/,'')
420
+ end
421
+ end
422
+ BinaryBlocker.register_klass(:utf16_string, FixedUTF16StringEncoder)
423
+ BinaryBlocker.register_klass(:utf16, FixedUTF16StringEncoder)
424
+
425
+ class PackedNumberEncoder < SimpleEncoder
426
+ def initialize(*opts)
427
+ initialize_options(*opts)
428
+
429
+ @length = @opts[:length].to_i
430
+ raise ArgumentError.new("Missing or invalid string length") unless @length > 0
431
+ @length += 1 if @length[0] == 1
432
+ @bytes = @length / 2
433
+ @format = "H#{@length}"
434
+
435
+ @key = @opts[:key]
436
+ @valid = @opts[:valid]
437
+
438
+ initialize_data(*opts)
439
+ end
440
+
441
+ def internal_block(val)
442
+ ["%0#{@length}d" % val].pack(@format)
443
+ end
444
+
445
+ def internal_deblock(io)
446
+ buffer = io.read(@bytes)
447
+ result = buffer.unpack(@format)
448
+ result.first.to_i
449
+ end
450
+ end
451
+ BinaryBlocker.register_klass(:packed, PackedNumberEncoder)
452
+
453
+ class PackedDateEncoder < PackedNumberEncoder
454
+ def initialize_options(*opts)
455
+ super
456
+ @opts[:length] = 8
457
+ end
458
+
459
+ def internal_block(val)
460
+ super val.year * 10000 + val.month * 100 + val.mday
461
+ end
462
+
463
+ def internal_deblock(io)
464
+ buffer = io.read(@bytes)
465
+ result = buffer.unpack(@format)
466
+ year, month, day = result.first.unpack("A4A2A2").map { |v| v.to_i }
467
+ Date.civil(year, month, day)
468
+ end
469
+
470
+ end
471
+ BinaryBlocker.register_klass(:date, PackedDateEncoder)
472
+
473
+ class PackedDateTimeEncoder < PackedNumberEncoder
474
+ def initialize_options(*opts)
475
+ super
476
+ @opts[:length] = 14
477
+ end
478
+
479
+ def internal_block(val)
480
+ super sprintf("%04d%02d%02d%02d%02d%02d", val.year, val.month, val.mday, val.hour, val.min, val.sec).to_i
481
+ end
482
+
483
+ def internal_deblock(io)
484
+ buffer = io.read(@bytes)
485
+ result = buffer.unpack(@format)
486
+ year, month, day, hour, min, sec = result.first.unpack("A4A2A2A2A2A2").map { |v| v.to_i }
487
+ Time.local(year, month, day, hour, min, sec)
488
+ end
489
+
490
+ end
491
+ BinaryBlocker.register_klass(:time, PackedDateTimeEncoder)
492
+ BinaryBlocker.register_klass(:datetime, PackedDateTimeEncoder)
493
+
494
+ class OneOfEncoder < Encoder
495
+
496
+ def inspect
497
+ "OneOf #{@classes.join(',')} -> #{@obj.class} -> #{@obj.inspect}"
498
+ end
499
+
500
+ def initialize(classes, *opts)
501
+ @classes = classes.map { |o| o.call(*opts) }
502
+ @obj = nil
503
+ super(*opts)
504
+ end
505
+
506
+ def internal_block(val)
507
+ val.block if val
508
+ end
509
+
510
+ def internal_deblock(io)
511
+ @obj = nil
512
+ with_guarded_io_pos(io) do
513
+ @classes.each do |obj|
514
+ if obj.deblock(io)
515
+ @obj = obj
516
+ break
517
+ end
518
+ false
519
+ end
520
+ end
521
+ @obj
522
+ end
523
+
524
+ def method_missing(sym, *args)
525
+ if @obj
526
+ @obj.send sym, *args
527
+ else
528
+ super
529
+ end
530
+ end
531
+
532
+ def valid?
533
+ @obj && @obj.valid?
534
+ end
535
+ end
536
+
537
+ class FixedArrayEncoder < GroupEncoder
538
+ def initialize(count, classes, *opts)
539
+ initialize_options(*opts)
540
+ @count = count
541
+ @classes = classes
542
+ @value = Array.new(count) { OneOfEncoder.new(classes, *opts) }
543
+ initialize_data(*opts)
544
+ end
545
+
546
+ def internal_block(val)
547
+ val.inject("") { |r, o| r + o.block }
548
+ end
549
+
550
+ def deblock(io)
551
+ result = []
552
+ with_guarded_io_pos(io) do
553
+ @count.times do
554
+ result << OneOfEncoder.new(@classes, io, @opts)
555
+ end
556
+ end
557
+ @value = result
558
+ end
559
+
560
+ def [](offset)
561
+ raise RangeError.new("Access (#{offset}) out of range (#{@count})") unless (0...@count) === offset
562
+ @value[offset]
563
+ end
564
+
565
+ def []=(offset,val)
566
+ raise RangeError.new("Access (#{offset}) out of range (#{@count})") unless (0...@count) === offset
567
+ @value[offset] = val
568
+ end
569
+ end
570
+
571
+ class ListOfEncoder < GroupEncoder
572
+ def initialize(classes, *opts)
573
+ initialize_options(*opts)
574
+ @count = 0
575
+ @classes = classes
576
+ @value = []
577
+ initialize_data(*opts)
578
+ end
579
+
580
+ def internal_block(val)
581
+ val.inject("") { |r, o| r + o.block }
582
+ end
583
+
584
+ def deblock(io)
585
+ result = []
586
+ with_guarded_io_pos(io) do
587
+ oe = OneOfEncoder.new(@classes, @opts)
588
+ while oe.deblock(io)
589
+ result << oe
590
+ oe = OneOfEncoder.new(@classes, @opts)
591
+ end
592
+ end
593
+ @value = result
594
+ end
595
+
596
+ def [](offset)
597
+ raise RangeError.new("Access (#{offset}) out of range (#{@value.size})") unless (0...@value.size) === offset
598
+ @value[offset]
599
+ end
600
+
601
+ def []=(offset,val)
602
+ raise RangeError.new("Access (#{offset}) out of range (#{@value.size})") unless (0...@value.size) === offset
603
+ @value[offset] = val
604
+ end
605
+
606
+ def <<(val)
607
+ @value << val
608
+ end
609
+
610
+ def size
611
+ @value.size
612
+ end
613
+ alias length size
614
+ end
615
+
616
+ class CountedArrayEncoder < GroupEncoder
617
+ def initialize(count_type, classes, *opts)
618
+ # this is dynamic now, but we init to zero for the range checking
619
+ @count_enc = BinaryBlocker.pack_symbols[count_type] || count_type
620
+ @count = 0
621
+ initialize_options(*opts)
622
+ @classes = classes
623
+ @value = []
624
+ initialize_data(*opts)
625
+ end
626
+
627
+ def internal_block(val)
628
+ buf = [val.size].pack(@count_enc)
629
+ val.inject(buf) { |r, o| r + o.block }
630
+ end
631
+
632
+ def deblock(io)
633
+ length = BinaryBlocker.sizeof_format(@count_enc)
634
+ result = []
635
+ with_guarded_io_pos(io) do
636
+ @count = io.read(length).unpack(@count_enc)
637
+ @count.times do
638
+ result << OneOfEncoder.new(@classes, io, @opts)
639
+ end
640
+ end
641
+ @value = result
642
+ end
643
+
644
+ def [](offset)
645
+ raise RangeError.new("Access (#{offset}) out of range (#{@count})") unless (0...@count) === offset
646
+ @value[offset]
647
+ end
648
+
649
+ def []=(offset,val)
650
+ raise RangeError.new("Access (#{offset}) out of range (#{@count})") unless (0...@count) === offset
651
+ @value[offset] = val
652
+ end
653
+
654
+ def <<(val)
655
+ @count += 1
656
+ @value << val
657
+ end
658
+
659
+ def size
660
+ @count
661
+ end
662
+ alias length size
663
+ end
664
+
665
+ class BitFieldEncoder < Encoder
666
+ def initialize(type, bit_info, *opts)
667
+ @type = BinaryBlocker.pack_symbols[type] || type
668
+ @length = BinaryBlocker.sizeof_format(@type)
669
+ @bit_info = {}
670
+ pos = 0
671
+ bit_info.each do |bi|
672
+ case bi
673
+ when Symbol
674
+ @bit_info[bi.to_sym] = [pos,1]
675
+ pos += 1
676
+
677
+ when Fixnum
678
+ pos += bi
679
+
680
+ when Array
681
+ @bit_info[bi.first.to_sym] = [pos, bi.last.to_i]
682
+ pos += bi.last.to_i
683
+ end
684
+ end
685
+ @value = 0
686
+ initialize_options(*opts)
687
+ initialize_data(*opts)
688
+ end
689
+
690
+ def internal_block(val)
691
+ [val.raw_value].pack(@type)
692
+ end
693
+
694
+ def internal_deblock(io)
695
+ buffer = io.read(@length)
696
+ result = buffer.unpack(@type)
697
+ result.first
698
+ end
699
+
700
+ def value
701
+ self
702
+ end
703
+
704
+ def raw_value
705
+ @value
706
+ end
707
+
708
+ def method_missing(sym, *args)
709
+ if (bi = @bit_info[sym])
710
+ v = @value >> bi.first
711
+ mask = (1 << bi.last) - 1
712
+ v = v & mask
713
+ else
714
+ sym = sym.to_s
715
+ if sym[-1] == ?=
716
+ if bi = @bit_info[sym[0..-2].to_sym]
717
+ @value &= ~(((1 << bi.last) - 1) << bi.first)
718
+ @value |= args.first.to_i << bi.first
719
+ return @value
720
+ end
721
+ end
722
+ raise NoMethodError.new("undefined method `#{sym}''")
723
+ # I was using super, but it was throwing a different error
724
+ # which seemed wrong, not sure why
725
+ end
726
+ end
727
+
728
+ end
729
+
730
+ class Blocker < GroupEncoder
731
+ def inspect
732
+ "Blocker: #{@value.inspect} (index: #{@lookup})"
733
+ end
734
+ end
735
+
736
+ end