arpie 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,754 @@
1
+ module Arpie
2
+
3
+ # A Binary is a helper to convert arbitary bitlevel binary data
4
+ # to and from ruby Struct-lookalikes.
5
+ #
6
+ # Here's an example:
7
+ #
8
+ # class Test < Arpie::Binary
9
+ # describe "I am a test"
10
+ #
11
+ # field :a, :uint8
12
+ # field :b, :bytes, :sizeof => :nint16
13
+ # end
14
+ #
15
+ # This will allow you to parse binary blocks consisting of:
16
+ # - a byte
17
+ # - a network-order int (16 bit)
18
+ # - a string which length' is the given int
19
+ # Rinse and repeat.
20
+ #
21
+ # For all available data types, please look into the source
22
+ # file of this class at the bottom.
23
+ #
24
+ # Writing new types is easy enough, (see BinaryType).
25
+ #
26
+ # On confusing names:
27
+ #
28
+ # Arpie uses the term +binary+ to refer to on-the-wire data bit/byte binary format.
29
+ # A +Binary+ (uppercase) is a object describing the format. If you're reading +binary+,
30
+ # think "raw data"; if you're reading +Binary+ or +object+, think Arpie::Binary.
31
+ #
32
+ # Another warning:
33
+ #
34
+ # Do not use +Kernel+ methods as field names. It'll confuse method_missing.
35
+ # Example:
36
+ # field :test, :uint8
37
+ # => in `test': wrong number of arguments (ArgumentError)
38
+ #
39
+ # In fact, this is the reason while Binary will not let you define fields with
40
+ # with names like existing instance methods.
41
+ class Binary
42
+ extend Arpie
43
+ class Field < Struct.new(:name, :type, :opts, :inline_handler) ; end
44
+ class Virtual < Struct.new(:name, :type, :opts, :handler) ; end
45
+
46
+
47
+ @@types ||= {}
48
+ @@fields ||= {}
49
+ @@virtuals ||= {}
50
+ @@description ||= {}
51
+ @@hooks ||= {}
52
+
53
+ #:stopdoc:
54
+ @@anonymous ||= {}
55
+ def self.__anonymous
56
+ @@anonymous[self]
57
+ end
58
+ def self.__anonymous= x
59
+ @@anonymous[self] = x
60
+ end
61
+ #:startdoc:
62
+
63
+ def initialize
64
+ @fields = {}
65
+
66
+ # set up our own class handlers, create anon classes, set up default values
67
+ @@fields[self.class].each {|field|
68
+ if field.inline_handler
69
+ @fields[field.name] = field.inline_handler.new
70
+
71
+ elsif field.type.is_a?(Class)
72
+ @fields[field.name] = field.type.new
73
+
74
+ elsif field.opts[:default]
75
+ @fields[field.name] = field.opts[:default]
76
+ end
77
+ }
78
+ if block_given?
79
+ yield self
80
+ end
81
+ end
82
+
83
+ def inspect #:nodoc:
84
+ desc = " " + @@description[self.class].inspect if @@description[self.class]
85
+ # Anonymous is special
86
+ klass = self.class.respond_to?(:__anonymous) && self.class.__anonymous ?
87
+ "Anon#{self.class.__anonymous.inspect}" :
88
+ self.class.to_s
89
+
90
+ "#<#{klass}#{desc} #{@fields.inspect}>"
91
+ end
92
+
93
+ def method_missing m, *a
94
+ m.to_s =~ /^(.+?)(=?)$/ or super
95
+ at = $1.to_sym
96
+ if self.class.field?(at)
97
+ if $2 == "="
98
+ a.size == 1 or raise ArgumentError
99
+ if !a[0].is_a?(Class) && inline = self.class.get_field(at)[3]
100
+ a[0], __nil = inline.from(a[0], {})
101
+ end
102
+ @fields[at] = a[0]
103
+ else
104
+ @fields[at]
105
+ end
106
+
107
+ elsif self.class.virtual?(at)
108
+ if $2 == "="
109
+ raise ArgumentError
110
+ else
111
+ Binary.call_virtual(self, at)
112
+ end
113
+
114
+ else
115
+ super
116
+ end
117
+ end
118
+
119
+ def self.call_virtual(on_object, name)
120
+ @@virtuals[on_object.class].select {|virtual|
121
+ virtual.name == name
122
+ }[0].handler.call(on_object)
123
+ end
124
+
125
+ # This registers a new type with this binary.
126
+ def self.register_type handler, *type_aliases
127
+ type_aliases.each do |type|
128
+ @@types[type] = handler
129
+ end
130
+ end
131
+
132
+ # Returns true if this Binary has the named +field+.
133
+ def self.field? field
134
+ @@fields[self] or return false
135
+ @@fields[self].select {|_field|
136
+ _field.name == field
137
+ }.size > 0
138
+ end
139
+
140
+ def self.get_field field
141
+ @@fields[self] or raise ArgumentError, "No such field: #{field.inspect}"
142
+ @@fields[self].each {|_field|
143
+ _field.name == field and return _field
144
+ }
145
+ raise ArgumentError, "No such field: #{field.inspect}"
146
+ end
147
+
148
+ # Returns true if this Binary has the named +virtual+.
149
+ def self.virtual? virtual
150
+ @@virtuals[self] or return false
151
+ @@virtuals[self].select {|_virtual|
152
+ _virtual.name == virtual
153
+ }.size > 0
154
+ end
155
+
156
+ # Returns the BinaryType handler class for +type+, which can be a
157
+ # symbol (:uint8), or a Arpie::Binary.
158
+ def self.get_type_handler type
159
+ if type.class === Arpie::Binary
160
+ type
161
+ else
162
+ @@types[type] or raise ArgumentError,
163
+ "#{self}: No such field type: #{type.inspect}"
164
+ end
165
+ end
166
+
167
+ def self.describe_all_types
168
+ ret = []
169
+ strf = "%-15s %-8s %s"
170
+ ret << strf % %w{TYPE WIDTH HANDLER}
171
+ @@types.sort{|a,b| a[0].to_s <=> b[0].to_s}.each {|type, handler|
172
+ ret << strf % [type.inspect, (handler.binary_size({}) rescue nil), handler.inspect]
173
+ }
174
+ ret.join("\n")
175
+ end
176
+
177
+ # You can use this to provide a short description of this Binary.
178
+ # It will be shown when calling Binary.inspect.
179
+ # When called without a parameter, (non-recursively) print out a
180
+ # pretty description of this data type as it would appear on the wire.
181
+ def self.describe text = nil
182
+ unless text.nil?
183
+ @@description[self] = text
184
+ else
185
+ ret = []
186
+ ret << "%-10s %s" % ["Binary:", @@description[self]]
187
+ ret << ""
188
+
189
+ sprf = "%-10s %-25s %-15s %-15s %-15s %s"
190
+ sprf_of = "%68s %s"
191
+ if @@virtuals[self] && @@virtuals[self].size > 0
192
+ ret << sprf % ["Virtuals:", "NAME", "TYPE", "WIDTH", "", "DESCRIPTION"]
193
+ @@virtuals[self].each {|virtual|
194
+ width = self.get_type_handler(virtual.type).binary_size(virtual.opts)
195
+ ret << sprf % [ "",
196
+ virtual.name,
197
+ virtual.type,
198
+ width,
199
+ "",
200
+ virtual.opts[:description]
201
+ ]
202
+ }
203
+ ret << ""
204
+ end
205
+ if @@fields[self] && @@fields[self].size > 0
206
+ ret << sprf % %w{Fields: NAME TYPE WIDTH OF DESCRIPTION}
207
+ @@fields[self].each {|field|
208
+ width = self.get_type_handler(field.type).binary_size(field.opts)
209
+ ret << sprf % [ "",
210
+ field.name,
211
+ field.type,
212
+ (field.opts[:length] || field.opts[:sizeof] || width),
213
+ field.opts[:of] ? field.opts[:of].inspect : "",
214
+ field.opts[:description]
215
+ ]
216
+ ret << sprf_of % [ "",
217
+ field.opts[:of_opts].inspect
218
+ ] if field.opts[:of_opts]
219
+ }
220
+ end
221
+ ret.join("\n")
222
+ end
223
+ end
224
+
225
+ # Specify that this Binary has a field of type +type+.
226
+ # See the class documentation for usage.
227
+ def self.field name, type = nil, opts = {}, &block
228
+ @@fields[self] ||= []
229
+ handler = get_type_handler(type)
230
+
231
+ raise ArgumentError, "#{name.inspect} already exists as a virtual" if virtual?(name)
232
+ raise ArgumentError, "#{name.inspect} already exists as a field" if field?(name)
233
+ raise ArgumentError, "#{name.inspect} already exists as a instance method" if instance_methods.index(name.to_s)
234
+ raise ArgumentError, "#{name.inspect}: cannot inline classes" if block_given? and type.class === Arpie::Binary
235
+
236
+ @@fields[self].each {|field|
237
+ raise ArgumentError, "#{name.inspect}: :optional fields cannot be followed by required fields" if
238
+ field[:opts].include?(:optional)
239
+ } unless opts[:optional]
240
+
241
+
242
+ type.nil? && !block_given? and raise ArgumentError,
243
+ "You need to specify an inline handler if no type is given."
244
+ inline_handler = nil
245
+
246
+ if block_given?
247
+ inline_handler = Class.new(Arpie::Binary)
248
+ inline_handler.__anonymous = [name, type, opts]
249
+ inline_handler.instance_eval(&block)
250
+ end
251
+
252
+ if type.nil?
253
+ type, inline_handler = inline_handler, nil
254
+ end
255
+
256
+ if handler.respond_to?(:required_opts)
257
+ missing_required = handler.required_opts.keys - opts.keys
258
+ raise ArgumentError, "#{self}: #{name.inspect} as type #{type.inspect} " +
259
+ "requires options: #{missing_required.inspect}" if missing_required.size > 0
260
+ handler.required_opts.each {|k,v|
261
+ v.nil? and next
262
+ v.call(opts[k]) or raise ArgumentError, "#{self}: Invalid value given for opt key #{k.inspect}."
263
+ }
264
+ end
265
+
266
+ opts[:description] ||= opts[:desc] if opts[:desc]
267
+ opts.delete(:desc)
268
+
269
+ @@fields[self] << Field.new(name.to_sym, type, opts, inline_handler)
270
+ end
271
+
272
+ # Set up a new virtual field
273
+ def self.virtual name, type, opts = {}, &handler
274
+ raise ArgumentError, "You need to pass a block with virtuals" unless block_given?
275
+ raise ArgumentError, "#{name.inspect} already exists as a virtual" if virtual?(name)
276
+ raise ArgumentError, "#{name.inspect} already exists as a field" if field?(name)
277
+ raise ArgumentError, "#{name.inspect} already exists as a instance method" if instance_methods.index(name.to_s)
278
+
279
+ @@virtuals[self] ||= []
280
+ opts[:description] ||= opts[:desc]
281
+ opts.delete(:desc)
282
+ @@virtuals[self] << Virtual.new(name.to_sym, type, opts, handler)
283
+ end
284
+
285
+
286
+ def self.binary_size opts = {}
287
+ @@fields[self] ||= []
288
+ total = @@fields[self].inject(0) {|sum, field|
289
+ klass = get_type_handler(field.type)
290
+ sum + klass.binary_size(field.opts)
291
+ }
292
+
293
+ total
294
+ end
295
+
296
+ def self.add_hook(hook, handler)
297
+ @@hooks[self] ||= {}
298
+ @@hooks[self][hook] ||= []
299
+ @@hooks[self][hook] << handler
300
+ end
301
+
302
+ def self.call_hooks(hook, *va)
303
+ @@hooks[self] ||= {}
304
+ @@hooks[self][hook] ||= []
305
+ @@hooks[self][hook].each {|handler|
306
+ va = *handler.call(*va)
307
+ }
308
+ va
309
+ end
310
+
311
+ # Parse the given +binary+, which is a string, and return an instance of this class.
312
+ # Will raise Arpie::EIncomplete when not enough data is available in +binary+ to construct
313
+ # a complete Binary.
314
+ def self.from binary, opts = {}
315
+ @@fields[self] ||= []
316
+ binary = * self.call_hooks(:pre_from, binary)
317
+
318
+ consumed_bytes = 0
319
+ obj = new
320
+ @@fields[self].each {|field| # name, klass, kopts, inline_handler|
321
+ field.opts[:binary] = binary
322
+ field.opts[:object] = obj
323
+ handler = get_type_handler(field.type)
324
+
325
+ attrib, consumed = binary, nil
326
+
327
+ attrib, consumed =
328
+ handler.from(binary[consumed_bytes .. -1], field.opts) rescue case $!
329
+ when EIncomplete
330
+ if field.opts[:optional]
331
+ attrib, consumed = field.opts[:default], handler.binary_size(field.opts)
332
+ else
333
+ raise $!,
334
+ "#{$!.to_s}, #{self}#from needs more data for " +
335
+ "#{field.name.inspect}. (data: #{binary[consumed_bytes .. -1].inspect})"
336
+ end
337
+ when StreamError
338
+ bogon! binary[consumed_bytes .. -1], "#{self}#from: #{field.name.inspect}: #{$!.to_s}"
339
+ else
340
+ raise
341
+ end
342
+ consumed_bytes += consumed
343
+
344
+ obj.send((field.name.to_s + "=").to_sym, attrib)
345
+ field.opts.delete(:binary)
346
+ field.opts.delete(:object)
347
+ }
348
+
349
+ binary, obj, consumed_bytes = self.call_hooks(:post_from, binary, obj, consumed_bytes)
350
+ [obj, consumed_bytes]
351
+ end
352
+
353
+ # Recursively convert the given Binary object to wire format.
354
+ def self.to object, opts = {}
355
+ object.nil? and raise ArgumentError, "cannot #to nil"
356
+ @@fields[self] ||= []
357
+ r = []
358
+ object = * self.call_hooks(:pre_to, object)
359
+
360
+ @@fields[self].each {|field| # name, klass, kopts, inline_handler|
361
+ field.opts[:object] = object
362
+ handler = get_type_handler(field.type)
363
+ val = object.send(field.name)
364
+
365
+ if field.inline_handler
366
+ val = val.to
367
+ end
368
+
369
+ # r << (val.respond_to?(:to) ? val.to(opts) : handler.to(val, kopts)) rescue case $!
370
+ r << handler.to(val, field.opts) rescue case $!
371
+ when StreamError
372
+ raise $!, "#{self}#from: #{field.name.inspect}: #{$!.to_s}"
373
+ else
374
+ raise
375
+ end
376
+ field.opts.delete(:object)
377
+ }
378
+
379
+ r = r.join('')
380
+ _obj, r = self.call_hooks(:post_to, object, r)
381
+ r
382
+ end
383
+
384
+ def to opts = {}
385
+ self.class.to(self, opts)
386
+ end
387
+
388
+ # Add a hook that gets called before converting a binary to
389
+ # Binary representation.
390
+ # Arguments to the handler: +binary+
391
+ # Note that all handlers need to return their arguemts as they were
392
+ # passed, as they will replace the original values.
393
+ def self.pre_to &handler
394
+ self.add_hook(:pre_to, handler)
395
+ end
396
+ # Add a hook that gets called after converting a binary to
397
+ # Binary representation.
398
+ # Arguments to the handler: +object+, +binary+, +consumed_bytes+.
399
+ # Note that all handlers need to return their arguemts as they were
400
+ # passed, as they will replace the original values.
401
+ def self.post_to &handler
402
+ self.add_hook(:post_to, handler)
403
+ end
404
+ # Add a hook that gets called before converting a Binary to
405
+ # wire format.
406
+ # Arguments to the handler: +object+
407
+ # Note that all handlers need to return their arguemts as they were
408
+ # passed, as they will replace the original values.
409
+ def self.pre_from &handler
410
+ self.add_hook(:pre_from, handler)
411
+ end
412
+ # Add a hook that gets called after converting a Binary to
413
+ # wire format.
414
+ # Arguments to the handler: +binary+, +object+
415
+ # Note that all handlers need to return their arguemts as they were
416
+ # passed, as they will replace the original values.
417
+ def self.post_from &handler
418
+ self.add_hook(:post_from, handler)
419
+ end
420
+ end
421
+
422
+ class BinaryType
423
+ include Arpie
424
+
425
+ def binary_size opts
426
+ nil
427
+ end
428
+
429
+ def required_opts
430
+ {}
431
+ end
432
+
433
+ # Return [object, len]
434
+ def from binary, opts
435
+ raise NotImplementedError
436
+ end
437
+
438
+ # Return [binary]
439
+ def to object, opts
440
+ raise NotImplementedError
441
+ end
442
+
443
+ def check_limit value, limit
444
+ case limit
445
+ when nil
446
+ true
447
+ when Range, Array
448
+ limit.include?(value)
449
+ else
450
+ raise ArgumentError, "unknown limit definition: #{limit.inspect}"
451
+ end or bogon! nil, "not in :limit => #{limit.inspect}"
452
+ end
453
+
454
+ end
455
+
456
+ class PackBinaryType < BinaryType
457
+ attr_reader :pack_string
458
+
459
+ def binary_size opts
460
+ opts = @force_opts.merge(opts || {})
461
+ PackBinaryType.length_of(@pack_string + (opts[:length] || 1).to_s)
462
+ end
463
+
464
+ def self.length_of format
465
+ length = 0
466
+ format.scan(/(\S_?)\s*(\d*)/).each do |directive, count|
467
+ count = count.to_i
468
+ count = 1 if count == 0
469
+
470
+ length += case directive
471
+ when 'A', 'a', 'C', 'c', 'Z', 'x' : count
472
+ when 'B', 'b' : (count / 8.0).ceil
473
+ when 'D', 'd', 'E', 'G' : count * 8
474
+ when 'e', 'F', 'f', 'g' : count * 4
475
+ when 'H', 'h' : (count / 2.0).ceil
476
+ when 'I', 'i', 'L', 'l', 'N', 'V' : count * 4
477
+ when 'n', 'S', 's', 'v' : count * 2
478
+ when 'Q', 'q' : count * 8
479
+ when 'X' : count * -1
480
+ else raise ArgumentError, "#{directive} is not supported"
481
+ end
482
+ end
483
+
484
+ length
485
+ end
486
+
487
+ def initialize pack_string, force_opts = {}
488
+ @pack_string = pack_string
489
+ @force_opts = force_opts
490
+ end
491
+
492
+ def from binary, opts
493
+ opts = @force_opts.merge(opts || {})
494
+ binary && binary.size >= binary_size(opts) or incomplete!
495
+ len = opts[:length] || 1
496
+ pack_string = @pack_string + len.to_s
497
+ value = binary.unpack(pack_string)[0]
498
+ value += opts[:mod] if opts[:mod]
499
+ check_limit value, opts[:limit]
500
+
501
+ [value, binary_size(opts)]
502
+ end
503
+
504
+ def to object, opts
505
+ opts = @force_opts.merge(opts || {})
506
+ object.nil? and bogon! nil,"nil object given"
507
+ object -= opts[:mod] if opts[:mod]
508
+ len = opts[:length] || 1
509
+ pack_string = @pack_string + len.to_s
510
+ [object].pack(pack_string)
511
+ end
512
+
513
+ end
514
+
515
+ Binary.register_type(PackBinaryType.new('c'), :uint8)
516
+ Binary.register_type(PackBinaryType.new("c"), :int8)
517
+ Binary.register_type(PackBinaryType.new("C"), :uint8)
518
+ Binary.register_type(PackBinaryType.new("s"), :int16)
519
+ Binary.register_type(PackBinaryType.new("S"), :uint16)
520
+ Binary.register_type(PackBinaryType.new("i"), :int32)
521
+ Binary.register_type(PackBinaryType.new("I"), :uint32)
522
+ Binary.register_type(PackBinaryType.new("q"), :int64)
523
+ Binary.register_type(PackBinaryType.new("Q"), :uint64)
524
+
525
+ Binary.register_type(PackBinaryType.new("l"), :long32)
526
+ Binary.register_type(PackBinaryType.new("L"), :ulong32)
527
+
528
+ Binary.register_type(PackBinaryType.new("n"), :nint16)
529
+ Binary.register_type(PackBinaryType.new("N"), :nint32)
530
+ Binary.register_type(PackBinaryType.new("v"), :lint16)
531
+ Binary.register_type(PackBinaryType.new("V"), :lint32)
532
+
533
+ Binary.register_type(PackBinaryType.new("d"), :double)
534
+ Binary.register_type(PackBinaryType.new("E"), :ldouble)
535
+ Binary.register_type(PackBinaryType.new("G"), :ndouble)
536
+
537
+ Binary.register_type(PackBinaryType.new("f"), :float)
538
+ Binary.register_type(PackBinaryType.new("e"), :lfloat)
539
+ Binary.register_type(PackBinaryType.new("g"), :nfloat)
540
+
541
+ Binary.register_type(PackBinaryType.new("B"), :msb_bitfield)
542
+ Binary.register_type(PackBinaryType.new("b"), :lsb_bitfield)
543
+
544
+ class BitBinaryType < Arpie::BinaryType
545
+ def from binary, opts
546
+ len = opts[:length] || 1
547
+ binary.size >= len or incomplete!
548
+ b = binary.split("")[0,len].map {|x|
549
+ x == "1"
550
+ }
551
+ b = b[0] if b.size == 1
552
+ [b, len]
553
+ end
554
+
555
+ def to object, opts
556
+ object = [object] if object.is_a?(TrueClass) || object.is_a?(FalseClass)
557
+ object.map {|x| x == true ? "1" : "0" }.join("")
558
+ end
559
+ end
560
+ Arpie::Binary.register_type(BitBinaryType.new, :bit)
561
+
562
+ class BytesBinaryType < BinaryType
563
+ def all_opts; [:sizeof, :length] end
564
+
565
+ def initialize pack_string, force_opts = {}
566
+ @pack_string = pack_string
567
+ @force_opts = force_opts
568
+ end
569
+
570
+ def binary_size opts
571
+ opts = @force_opts.merge(opts || {})
572
+ if opts[:sizeof]
573
+ len_handler = Binary.get_type_handler(opts[:sizeof])
574
+ len_handler.binary_size(opts[:sizeof_opts])
575
+ elsif opts[:length]
576
+ opts[:length]
577
+ else
578
+ nil
579
+ end
580
+ end
581
+
582
+ def from binary, opts
583
+ opts = (opts || {}).merge(@force_opts)
584
+ if opts[:sizeof]
585
+ len_handler = Binary.get_type_handler(opts[:sizeof])
586
+ len, len_size = len_handler.from(binary, opts[:sizeof_opts])
587
+ binary.size >= len_size + len or incomplete!
588
+
589
+ [binary.unpack("x#{len_size} #{@pack_string}#{len}")[0], len_size + len]
590
+
591
+ elsif opts[:length]
592
+ len = case opts[:length]
593
+ when :all
594
+ binary.size
595
+ else
596
+ opts[:length]
597
+ end
598
+ binary.size >= len or incomplete!
599
+ [binary.unpack("#{@pack_string}#{len}")[0], len]
600
+
601
+ else
602
+ raise ArgumentError, "need one of [:sizeof, :length]"
603
+ end
604
+
605
+ end
606
+
607
+ def to object, opts
608
+ opts = (opts || {}).merge(@force_opts)
609
+ if opts[:sizeof]
610
+ len_handler = Binary.get_type_handler(opts[:sizeof])
611
+ len_handler.respond_to?(:pack_string) or raise ArgumentError,
612
+ "#{self.class}#to: needs a PackStringType parameter for length"
613
+
614
+ [object.size, object].pack("#{len_handler.pack_string} #{@pack_string}*")
615
+
616
+ elsif opts[:length]
617
+ len = case opts[:length]
618
+ when :all
619
+ "*"
620
+ when Symbol
621
+ opts[:object].send(opts[:length])
622
+ else
623
+ opts[:length]
624
+ end
625
+ [object].pack("#{@pack_string}#{len}")
626
+
627
+ else
628
+ raise ArgumentError, "need one of [:sizeof, :length]"
629
+ end
630
+
631
+ end
632
+ end
633
+
634
+ Binary.register_type(BytesBinaryType.new("a", :length => 1), :char)
635
+ Binary.register_type(BytesBinaryType.new("a"), :bytes)
636
+ Binary.register_type(BytesBinaryType.new("A"), :string)
637
+ Binary.register_type(BytesBinaryType.new("Z"), :nstring)
638
+
639
+ Binary.register_type(BytesBinaryType.new("M"), :quoted_printable)
640
+ Binary.register_type(BytesBinaryType.new("m"), :base64)
641
+ Binary.register_type(BytesBinaryType.new("u"), :uuencoded)
642
+
643
+
644
+ class ListBinaryType < BinaryType
645
+
646
+ def binary_size opts
647
+ if opts[:sizeof]
648
+ len_handler = Binary.get_type_handler(opts[:sizeof])
649
+ len_handler.binary_size(opts[:sizeof_opts])
650
+ elsif opts[:length]
651
+ case opts[:length]
652
+ when Symbol
653
+ opts[:object] ? opts[:object].send(opts[:length]) : nil
654
+ else
655
+ opts[:length]
656
+ end
657
+ else
658
+ nil
659
+ end
660
+ end
661
+
662
+ def from binary, opts
663
+ type_of = Binary.get_type_handler(opts[:of])
664
+ type_of.respond_to?(:binary_size) &&
665
+ type_of_binary_size = type_of.binary_size(opts[:of_opts]) or raise ArgumentError,
666
+ "can only encode known-width fields"
667
+
668
+ list = []
669
+ consumed = 0
670
+ length = nil
671
+
672
+ if opts[:sizeof]
673
+ len_handler = Binary.get_type_handler(opts[:sizeof])
674
+ length, ate = len_handler.from(binary, opts[:sizeof_opts])
675
+ consumed += ate
676
+
677
+ elsif opts[:length]
678
+ length = case opts[:length]
679
+ when :all
680
+ binary.size / type_of_binary_size
681
+ when Symbol
682
+ opts[:object].send(opts[:length])
683
+ else
684
+ opts[:length]
685
+ end
686
+ else
687
+ raise ArgumentError, "need one of [:sizeof, :length]"
688
+ end
689
+
690
+ cc, ate = nil, nil
691
+ for i in 0...length do
692
+ cc, ate = type_of.from(binary[consumed .. -1], opts[:of_opts])
693
+ list << cc
694
+ consumed += ate
695
+ end
696
+
697
+ [list, consumed]
698
+ end
699
+
700
+ def to object, opts
701
+ object.is_a?(Array) or bogon! object, "require Array"
702
+
703
+ type_of = Binary.get_type_handler(opts[:of])
704
+
705
+ if opts[:sizeof]
706
+ len_handler = Binary.get_type_handler(opts[:sizeof])
707
+ ([len_handler.to(object.size, opts[:sizeof_opts])] + object.map {|o|
708
+ type_of.to(o, opts[:of_opts])
709
+ }).join('')
710
+
711
+ elsif opts[:length]
712
+ length = case opts[:length]
713
+ when Symbol
714
+ opts[:object].send(opts[:length])
715
+ else
716
+ opts[:length]
717
+ end
718
+
719
+ object.size == length or bogon! object,
720
+ "Array#size does not match required fixed width: " +
721
+ "have #{object.size}, require #{length.inspect}"
722
+
723
+ object.map {|o|
724
+ type_of.to(o, opts[:of_opts])
725
+ }.join('')
726
+
727
+ else
728
+ raise ArgumentError, "need one of [:sizeof, :length]"
729
+ end
730
+
731
+ end
732
+ end
733
+ Binary.register_type(ListBinaryType.new, :list)
734
+
735
+ class FixedBinaryType < BinaryType
736
+ def required_opts ; {:value => proc {|v| v.is_a?(String)}} end
737
+ def binary_size opts
738
+ opts[:value].size
739
+ end
740
+
741
+ def from binary, opts
742
+ sz = opts[:value].size
743
+ existing = binary.unpack("a#{sz}")[0]
744
+ existing == opts[:value] or bogon! nil, ":fixed did not match data in packet"
745
+
746
+ [opts[:value], opts[:value].size]
747
+ end
748
+
749
+ def to object, opts
750
+ opts[:value]
751
+ end
752
+ end
753
+ Binary.register_type(FixedBinaryType.new, :fixed)
754
+ end