binaryparse 0.1.0

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.
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