arpie 0.0.4 → 0.0.5

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