bit-struct 0.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/.gitignore +2 -0
  2. data/History.txt +102 -0
  3. data/README.txt +189 -0
  4. data/Rakefile +34 -0
  5. data/TODO +23 -0
  6. data/TODO-ALSO +71 -0
  7. data/examples/ara-player-data.rb +82 -0
  8. data/examples/bignum.rb +18 -0
  9. data/examples/bits.rb +19 -0
  10. data/examples/byte-bdy.rb +30 -0
  11. data/examples/field-ripper.rb +22 -0
  12. data/examples/fixed-point.rb +17 -0
  13. data/examples/ip.rb +81 -0
  14. data/examples/longlong.rb +30 -0
  15. data/examples/md.rb +23 -0
  16. data/examples/modular-def.rb +38 -0
  17. data/examples/native.rb +31 -0
  18. data/examples/nested.rb +33 -0
  19. data/examples/pad.rb +14 -0
  20. data/examples/ping-recv.rb +25 -0
  21. data/examples/ping.rb +73 -0
  22. data/examples/player-data.rb +75 -0
  23. data/examples/raw.rb +62 -0
  24. data/examples/rest.rb +30 -0
  25. data/examples/switch-endian.rb +49 -0
  26. data/examples/vector.rb +98 -0
  27. data/lib/bit-struct.rb +6 -0
  28. data/lib/bit-struct/bit-struct.rb +549 -0
  29. data/lib/bit-struct/char-field.rb +48 -0
  30. data/lib/bit-struct/fields.rb +273 -0
  31. data/lib/bit-struct/float-field.rb +61 -0
  32. data/lib/bit-struct/hex-octet-field.rb +20 -0
  33. data/lib/bit-struct/nested-field.rb +76 -0
  34. data/lib/bit-struct/octet-field.rb +45 -0
  35. data/lib/bit-struct/pad-field.rb +15 -0
  36. data/lib/bit-struct/signed-field.rb +258 -0
  37. data/lib/bit-struct/text-field.rb +44 -0
  38. data/lib/bit-struct/unsigned-field.rb +248 -0
  39. data/lib/bit-struct/vector-field.rb +77 -0
  40. data/lib/bit-struct/vector.rb +173 -0
  41. data/lib/bit-struct/yaml.rb +69 -0
  42. data/tasks/ann.rake +80 -0
  43. data/tasks/bones.rake +20 -0
  44. data/tasks/gem.rake +201 -0
  45. data/tasks/git.rake +40 -0
  46. data/tasks/notes.rake +27 -0
  47. data/tasks/post_load.rake +34 -0
  48. data/tasks/rdoc.rake +51 -0
  49. data/tasks/rubyforge.rake +55 -0
  50. data/tasks/setup.rb +292 -0
  51. data/tasks/spec.rake +54 -0
  52. data/tasks/svn.rake +47 -0
  53. data/tasks/test.rake +40 -0
  54. data/tasks/zentest.rake +36 -0
  55. data/test/test-endian.rb +39 -0
  56. data/test/test-vector.rb +38 -0
  57. data/test/test.rb +433 -0
  58. metadata +126 -0
@@ -0,0 +1,75 @@
1
+ require 'bit-struct'
2
+
3
+ class PlayerData < BitStruct
4
+
5
+ # type accessor size (bits) description (for #inspect_detailed)
6
+ unsigned :pid, 32, "Player ID"
7
+ float :x_position, 32, "X position", :format => "%8.3f"
8
+ float :y_position, 32, "Y position"
9
+ float :z_position, 32, "Z position"
10
+ unsigned :foobar, 32, "Foobar"
11
+
12
+ # def self.create(*args)
13
+ # new(args.pack(field_format))
14
+ # end
15
+
16
+ end
17
+
18
+ pd = PlayerData.new(
19
+ :pid => 400,
20
+ :x_position => 1,
21
+ :y_position => 2,
22
+ :z_position => 3,
23
+ :foobar => 0b101010
24
+ )
25
+
26
+ p pd
27
+ p pd.pid
28
+ p pd.x_position
29
+ p pd.foobar
30
+ p String.new(pd)
31
+ puts pd.inspect_detailed
32
+
33
+ params = {
34
+ :pid => 400,
35
+ :x_position => 1,
36
+ :y_position => 2,
37
+ :z_position => 3,
38
+ :foobar => 0b101010
39
+ }
40
+ param_string = String.new(pd)
41
+
42
+ id, x_position, y_position, z_position, foobar =
43
+ 400, 1.0, 2.0, 3.0, 0b101010
44
+
45
+ begin
46
+ require 'timeout'
47
+ n = 0
48
+ sec = 10
49
+ Timeout::timeout(sec) do
50
+ loop do
51
+ PlayerData.new params
52
+ #PlayerData.create id, x_position, y_position, z_position, foobar
53
+ n += 1
54
+ end
55
+ end
56
+ rescue Timeout::Error
57
+ puts "creations per second : #{ n / sec }"
58
+ end
59
+
60
+ __END__
61
+
62
+ #<PlayerData pid=400, x_position=1, y_position=2, z_position=3, foobar=42>
63
+ 400
64
+ 1
65
+ 42
66
+ "\000\000\001\220\000\000\000\001\000\000\000\002\000\000\000\003\000\000\000*"
67
+ PlayerData:
68
+ Player ID = 400
69
+ X position = 1
70
+ Y position = 2
71
+ Z position = 3
72
+ Foobar = 42
73
+ creations per second : 22428 # using params
74
+ creations per second : 243217 # using param_string
75
+ creations per second : 94254 # using create
@@ -0,0 +1,62 @@
1
+ require "socket"
2
+ require "./ip"
3
+
4
+ # A more substantial example of sending and receiving RAW packets.
5
+
6
+ begin
7
+ rsock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
8
+ rescue Errno::EPERM
9
+ $stderr.puts "Must run #{$0} as root."
10
+ exit!
11
+ end
12
+
13
+ begin
14
+ ssock = Socket.open(Socket::PF_INET, Socket::SOCK_RAW, Socket::IPPROTO_RAW)
15
+ unless ssock.getsockopt(Socket::SOL_IP, Socket::IP_HDRINCL)
16
+ puts "IP_HDRINCL is supposed to be the default for IPPROTO_RAW!"
17
+ puts "setting IP_HDRINCL anyway"
18
+ ssock.setsockopt(Socket::SOL_IP, Socket::IP_HDRINCL, true)
19
+ end
20
+ rescue Errno::EPERM
21
+ $stderr.puts "Must run #{$0} as root."
22
+ exit!
23
+ end
24
+
25
+ Thread.new do
26
+ loop do
27
+ data, sender = rsock.recvfrom(8192)
28
+ port, host = Socket.unpack_sockaddr_in(sender)
29
+ out = "-"*80,
30
+ "packet received from #{host}:#{port}:",
31
+ IP.new(data).inspect_detailed,
32
+ "-"*80
33
+ puts out
34
+ $stdout.flush
35
+ end
36
+ end
37
+
38
+ addr = Socket.pack_sockaddr_in(1024, "localhost")
39
+ 3.times do |i|
40
+ ip = IP.new do |b|
41
+ # ip_v and ip_hl are set for us by IP class
42
+ b.ip_tos = 0
43
+ b.ip_id = i+1
44
+ b.ip_off = 0
45
+ b.ip_ttl = 64
46
+ b.ip_p = Socket::IPPROTO_RAW
47
+ b.ip_src = "127.0.0.1"
48
+ b.ip_dst = "127.0.0.1"
49
+ b.body = "just another IP hacker"
50
+ b.ip_len = b.length
51
+ b.ip_sum = 0 # linux will calculate this for us (QNX won't?)
52
+ end
53
+
54
+ out = "-"*80,
55
+ "packet sent:",
56
+ ip.inspect_detailed,
57
+ "-"*80
58
+ puts out
59
+ $stdout.flush
60
+ ssock.send(ip, 0, addr)
61
+ sleep 1
62
+ end
@@ -0,0 +1,30 @@
1
+ require './ip'
2
+
3
+ class Point < BitStruct
4
+ unsigned :x, 16
5
+ unsigned :y, 16
6
+ unsigned :z, 16
7
+ end
8
+
9
+ class MyPacket < IP
10
+ rest :point, Point # treat this field as Point
11
+ end
12
+
13
+ packet = MyPacket.new
14
+ point = Point.new({:x=>1, :y=>2, :z=>3})
15
+ packet.point = point
16
+
17
+ p point
18
+ p packet.point
19
+ p packet
20
+
21
+ puts packet.inspect_detailed
22
+ puts "-" * 60
23
+
24
+ require 'yaml'
25
+
26
+ packet_yaml = packet.to_yaml
27
+ puts packet_yaml
28
+
29
+ packet2 = YAML.load( packet_yaml)
30
+ p packet2
@@ -0,0 +1,49 @@
1
+ module ModuleMethodSaver
2
+ def method_missing(meth, *args, &block)
3
+ @saved ||= []
4
+ @saved << [meth, args, block]
5
+ end
6
+
7
+ def included(m)
8
+ if @saved
9
+ @saved.each do |meth, args, block|
10
+ m.send(meth, *args, &block)
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+
17
+ require 'bit-struct'
18
+
19
+ module MyFields
20
+ extend ModuleMethodSaver
21
+
22
+ unsigned :x, 16
23
+ end
24
+
25
+ class LittleEndian < BitStruct
26
+ default_options :endian => :little
27
+ include MyFields
28
+ end
29
+
30
+ class BigEndian < BitStruct
31
+ default_options :endian => :big
32
+ include MyFields
33
+ end
34
+
35
+ le = LittleEndian.new
36
+ be = BigEndian.new
37
+
38
+ le.x = be.x = 256
39
+
40
+ p [le, le.to_s]
41
+ p [be, be.to_s]
42
+
43
+
44
+ __END__
45
+
46
+ Output:
47
+
48
+ [#<LittleEndian x=256>, "\000\001"]
49
+ [#<BigEndian x=256>, "\001\000"]
@@ -0,0 +1,98 @@
1
+ require 'bit-struct'
2
+
3
+ # Example 1
4
+ class Vec < BitStruct::Vector
5
+ # these declarations apply to *each* entry in the vector:
6
+ unsigned :x, 16
7
+ signed :y, 32
8
+
9
+ Entry = struct_class # Give it a name, just for inspect to look nice
10
+ end
11
+
12
+ v = Vec.new(5) # explicit length
13
+ entry = v[3]
14
+ entry.x = 2
15
+ v[3] = entry
16
+
17
+ entry = v[4]
18
+ entry.y = -4
19
+ v[4] = entry
20
+
21
+ entry.x = 42
22
+ v << entry
23
+
24
+ p v
25
+ puts
26
+
27
+ v2 = Vec.new(v) # determines length from argument
28
+ p v2
29
+ puts
30
+
31
+ # Example 2
32
+ class Vec2 < BitStruct::Vector
33
+ class Entry < BitStruct
34
+ unsigned :x, 16
35
+ signed :y, 32
36
+ end
37
+
38
+ struct_class Entry # use Entry as the struct_class for Vec2
39
+ end
40
+
41
+ v = Vec2.new(5) # explicit length
42
+ entry = v[3]
43
+ entry.x = 2
44
+ v[3] = entry
45
+
46
+ entry = v[4]
47
+ entry.y = -4
48
+ v[4] = entry
49
+
50
+ p v
51
+ puts
52
+
53
+ puts v.inspect_detailed
54
+ puts
55
+
56
+ puts Vec2.describe
57
+
58
+ # Example 3: inheritance
59
+ class VecSub < Vec2
60
+ float :z, 32
61
+ end
62
+
63
+ vsub = VecSub.new(3)
64
+ entry = vsub[1]
65
+ entry.x = 12
66
+ entry.y = -1
67
+ entry.z = 4.5
68
+ vsub[1] = entry
69
+ p vsub
70
+ puts
71
+
72
+ # Example 4: vector field in a bitstruct
73
+ class Packet < BitStruct
74
+ unsigned :stuff, 32, "whatever"
75
+
76
+ # Using the Vec class defined above
77
+ vector :v, Vec, "a vector", :length => 5
78
+
79
+ # equivalently, using an anonymous subclass of BitStruct::Vector
80
+ vector :v2, "a vector 2", :length => 5 do
81
+ unsigned :x, 16
82
+ signed :y, 32
83
+ end
84
+ end
85
+
86
+ pkt = Packet.new
87
+ vec = pkt.v
88
+ entry = vec[2]
89
+ entry.x = 123
90
+ entry.y = -456
91
+ vec[2] = entry
92
+ pkt.v = vec
93
+ p pkt, pkt.v, pkt.v[2], pkt.v[2].y
94
+ puts
95
+ puts pkt.inspect_detailed
96
+ puts
97
+
98
+ puts Packet.describe(:expand => true)
@@ -0,0 +1,6 @@
1
+ # A Convenience to load all field classes and yaml handling.
2
+
3
+ require 'bit-struct/bit-struct'
4
+ require 'bit-struct/fields'
5
+ require 'bit-struct/yaml'
6
+
@@ -0,0 +1,549 @@
1
+ # Class for packed binary data, with defined bitfields and accessors for them.
2
+ # See {intro.txt}[link:../doc/files/intro_txt.html] for an overview.
3
+ #
4
+ # Data after the end of the defined fields is accessible using the +rest+
5
+ # declaration. See examples/ip.rb. Nested fields can be declared using +nest+.
6
+ # See examples/nest.rb.
7
+ #
8
+ # Note that all string methods are still available: length, grep, etc.
9
+ # The String#replace method is useful.
10
+ #
11
+ class BitStruct < String
12
+ VERSION = "0.13.1"
13
+
14
+ class Field
15
+ # Offset of field in bits.
16
+ attr_reader :offset
17
+
18
+ # Length of field in bits.
19
+ attr_reader :length
20
+ alias size length
21
+
22
+ # Name of field (used for its accessors).
23
+ attr_reader :name
24
+
25
+ # Options, such as :default (varies for each field subclass).
26
+ # In general, options can be provided as strings or as symbols.
27
+ attr_reader :options
28
+
29
+ # Display name of field (used for printing).
30
+ attr_reader :display_name
31
+
32
+ # Default value.
33
+ attr_reader :default
34
+
35
+ # Format for printed value of field.
36
+ attr_reader :format
37
+
38
+ # Subclasses can override this to define a default for all fields of this
39
+ # class, not just the one currently being added to a BitStruct class, a
40
+ # "default default" if you will. The global default, if #default returns
41
+ # nil, is to fill the field with zero. Most field classes just let this
42
+ # default stand. The default can be overridden per-field when a BitStruct
43
+ # class is defined.
44
+ def self.default; nil; end
45
+
46
+ # Used in describe.
47
+ def self.class_name
48
+ @class_name ||= name[/\w+$/]
49
+ end
50
+
51
+ # Used in describe. Can be overridden per-subclass, as in NestedField.
52
+ def class_name
53
+ self.class.class_name
54
+ end
55
+
56
+ # Yield the description of this field, as an array of 5 strings: byte
57
+ # offset, type, name, size, and description. The opts hash may have:
58
+ #
59
+ # :expand :: if the value is true, expand complex fields
60
+ #
61
+ # (Subclass implementations may yield more than once for complex fields.)
62
+ #
63
+ def describe opts
64
+ bits = size
65
+ if bits > 32 and bits % 8 == 0
66
+ len_str = "%dB" % (bits/8)
67
+ else
68
+ len_str = "%db" % bits
69
+ end
70
+
71
+ byte_offset = offset / 8 + (opts[:byte_offset] || 0)
72
+
73
+ yield ["@%d" % byte_offset, class_name, name, len_str, display_name]
74
+ end
75
+
76
+ # Options are _display_name_, _default_, and _format_ (subclasses of Field
77
+ # may add other options).
78
+ def initialize(offset, length, name, opts = {})
79
+ @offset, @length, @name, @options =
80
+ offset, length, name, opts
81
+
82
+ @display_name = opts[:display_name] || opts["display_name"]
83
+ @default = opts[:default] || opts["default"] || self.class.default
84
+ @format = opts[:format] || opts["format"]
85
+ end
86
+
87
+ # Inspect the value of this field in the specified _obj_.
88
+ def inspect_in_object(obj, opts)
89
+ val = obj.send(name)
90
+ str =
91
+ begin
92
+ val.inspect(opts)
93
+ rescue ArgumentError # assume: "wrong number of arguments (1 for 0)"
94
+ val.inspect
95
+ end
96
+ (f=@format) ? (f % str) : str
97
+ end
98
+
99
+ # Normally, all fields show up in inspect, but some, such as padding,
100
+ # should not.
101
+ def inspectable?; true; end
102
+ end
103
+
104
+ NULL_FIELD = Field.new(0, 0, :null, :display_name => "null field")
105
+
106
+ # Raised when a field is added after an instance has been created. Fields
107
+ # cannot be added after this point.
108
+ class ClosedClassError < StandardError; end
109
+
110
+ # Raised if the chosen field name is not allowed, either because another
111
+ # field by that name exists, or because a method by that name exists.
112
+ class FieldNameError < StandardError; end
113
+
114
+ @default_options = {}
115
+
116
+ @initial_value = nil
117
+ @closed = nil
118
+ @rest_field = nil
119
+ @note = nil
120
+
121
+ class << self
122
+ def inherited cl
123
+ cl.instance_eval do
124
+ @initial_value = nil
125
+ @closed = nil
126
+ @rest_field = nil
127
+ @note = nil
128
+ end
129
+ end
130
+
131
+ # ------------------------
132
+ # :section: field access methods
133
+ #
134
+ # For introspection and metaprogramming.
135
+ #
136
+ # ------------------------
137
+
138
+ # Return the list of fields for this class.
139
+ def fields
140
+ @fields ||= self == BitStruct ? [] : superclass.fields.dup
141
+ end
142
+
143
+ # Return the list of fields defined by this class, not inherited
144
+ # from the superclass.
145
+ def own_fields
146
+ @own_fields ||= []
147
+ end
148
+
149
+ # Add a field to the BitStruct (usually, this is only used internally).
150
+ def add_field(name, length, opts = {})
151
+ round_byte_length ## just to make sure this has been calculated
152
+ ## before adding anything
153
+
154
+ name = name.to_sym
155
+
156
+ if @closed
157
+ raise ClosedClassError, "Cannot add field #{name}: " +
158
+ "The definition of the #{self.inspect} BitStruct class is closed."
159
+ end
160
+
161
+ if fields.find {|f|f.name == name}
162
+ raise FieldNameError, "Field #{name} is already defined as a field."
163
+ end
164
+
165
+ if instance_methods(true).find {|m| m == name}
166
+ if opts[:allow_method_conflict] || opts["allow_method_conflict"]
167
+ warn "Field #{name} is already defined as a method."
168
+ else
169
+ raise FieldNameError,"Field #{name} is already defined as a method."
170
+ end
171
+ end
172
+
173
+ field_class = opts[:field_class]
174
+
175
+ prev = fields[-1] || NULL_FIELD
176
+ offset = prev.offset + prev.length
177
+ field = field_class.new(offset, length, name, opts)
178
+ field.add_accessors_to(self)
179
+ fields << field
180
+ own_fields << field
181
+ @bit_length += field.length
182
+ @round_byte_length = (bit_length/8.0).ceil
183
+
184
+ if @initial_value
185
+ diff = @round_byte_length - @initial_value.length
186
+ if diff > 0
187
+ @initial_value << "\0" * diff
188
+ end
189
+ end
190
+
191
+ field
192
+ end
193
+
194
+ def parse_options(ary, default_name, default_field_class) # :nodoc:
195
+ opts = ary.grep(Hash).first || {}
196
+ opts = default_options.merge(opts)
197
+
198
+ opts[:display_name] = ary.grep(String).first || default_name
199
+ opts[:field_class] = ary.grep(Class).first || default_field_class
200
+
201
+ opts
202
+ end
203
+
204
+ # Get or set the hash of default options for the class, which apply to all
205
+ # fields. Changes take effect immediately, so can be used alternatingly with
206
+ # blocks of field declarations. If +h+ is provided, update the default
207
+ # options with that hash. Default options are inherited.
208
+ #
209
+ # This is especially useful with the <tt>:endian => val</tt> option.
210
+ def default_options h = nil
211
+ @default_options ||= superclass.default_options.dup
212
+ if h
213
+ @default_options.merge! h
214
+ end
215
+ @default_options
216
+ end
217
+
218
+ # Length, in bits, of this object.
219
+ def bit_length
220
+ @bit_length ||= fields.inject(0) {|a, f| a + f.length}
221
+ end
222
+
223
+ # Length, in bytes (rounded up), of this object.
224
+ def round_byte_length
225
+ @round_byte_length ||= (bit_length/8.0).ceil
226
+ end
227
+
228
+ def closed! # :nodoc:
229
+ @closed = true
230
+ end
231
+
232
+ def field_by_name name
233
+ @field_by_name ||= {}
234
+ field = @field_by_name[name]
235
+ unless field
236
+ field = fields.find {|f| f.name == name}
237
+ @field_by_name[name] = field if field
238
+ end
239
+ field
240
+ end
241
+ end
242
+
243
+ # Return the list of fields for this class.
244
+ def fields
245
+ self.class.fields
246
+ end
247
+
248
+ # Return the field with the given name.
249
+ def field_by_name name
250
+ self.class.field_by_name name
251
+ end
252
+
253
+ # ------------------------
254
+ # :section: metadata inspection methods
255
+ #
256
+ # Methods to textually describe the format of a BitStruct subclass.
257
+ #
258
+ # ------------------------
259
+
260
+ class << self
261
+ # Default format for describe. Fields are byte, type, name, size,
262
+ # and description.
263
+ DESCRIBE_FORMAT = "%8s: %-12s %-14s[%4s] %s"
264
+
265
+ # Can be overridden to use a different format.
266
+ def describe_format
267
+ DESCRIBE_FORMAT
268
+ end
269
+
270
+ # Textually describe the fields of this class of BitStructs.
271
+ # Returns a printable table (array of line strings), based on +fmt+,
272
+ # which defaults to #describe_format, which defaults to +DESCRIBE_FORMAT+.
273
+ def describe(fmt = nil, opts = {})
274
+ if fmt.kind_of? Hash
275
+ opts = fmt; fmt = nil
276
+ end
277
+
278
+ if block_given?
279
+ fields.each do |field|
280
+ field.describe(opts) do |desc|
281
+ yield desc
282
+ end
283
+ end
284
+ nil
285
+
286
+ else
287
+ fmt ||= describe_format
288
+
289
+ result = []
290
+
291
+ unless opts[:omit_header]
292
+ result << fmt % ["byte", "type", "name", "size", "description"]
293
+ result << "-"*70
294
+ end
295
+
296
+ fields.each do |field|
297
+ field.describe(opts) do |desc|
298
+ result << fmt % desc
299
+ end
300
+ end
301
+
302
+ unless opts[:omit_footer]
303
+ result << @note if @note
304
+ end
305
+
306
+ result
307
+ end
308
+ end
309
+
310
+ # Subclasses can use this to append a string (or several) to the #describe
311
+ # output. Notes are not cumulative with inheritance. When used with no
312
+ # arguments simply returns the note string
313
+ def note(*str)
314
+ @note = str unless str.empty?
315
+ @note
316
+ end
317
+ end
318
+
319
+ # ------------------------
320
+ # :section: initialization and conversion methods
321
+ #
322
+ # ------------------------
323
+
324
+ # Initialize the string with the given string or bitstruct, or with a hash of
325
+ # field=>value pairs, or with the defaults for the BitStruct subclass, or
326
+ # with an IO or other object with a #read method. Fields can be strings or
327
+ # symbols. Finally, if a block is given, yield the instance for modification
328
+ # using accessors.
329
+ def initialize(value = nil) # :yields: instance
330
+ self << self.class.initial_value
331
+
332
+ case value
333
+ when Hash
334
+ value.each do |k, v|
335
+ send "#{k}=", v
336
+ end
337
+
338
+ when nil
339
+
340
+ else
341
+ if value.respond_to?(:read)
342
+ value = value.read(self.class.round_byte_length)
343
+ end
344
+
345
+ self[0, value.length] = value
346
+ end
347
+
348
+ self.class.closed!
349
+ yield self if block_given?
350
+ end
351
+
352
+ DEFAULT_TO_H_OPTS = {
353
+ :convert_keys => :to_sym,
354
+ :include_rest => true
355
+ }
356
+
357
+ # Returns a hash of {name=>value,...} for each field. By default, include
358
+ # the rest field.
359
+ # Keys are symbols derived from field names using +to_sym+, unless
360
+ # <tt>opts[:convert_keys]<\tt> is set to some other method name.
361
+ def to_h(opts = DEFAULT_TO_H_OPTS)
362
+ converter = opts[:convert_keys] || :to_sym
363
+
364
+ fields_for_to_h = fields
365
+ if opts[:include_rest] and (rest_field = self.class.rest_field)
366
+ fields_for_to_h += [rest_field]
367
+ end
368
+
369
+ fields_for_to_h.inject({}) do |h,f|
370
+ h[f.name.send(converter)] = send(f.name)
371
+ h
372
+ end
373
+ end
374
+
375
+ # Returns an array of values of the fields of the BitStruct. By default,
376
+ # include the rest field.
377
+ def to_a(include_rest = true)
378
+ ary =
379
+ fields.map do |f|
380
+ send(f.name)
381
+ end
382
+
383
+ if include_rest and (rest_field = self.class.rest_field)
384
+ ary << send(rest_field.name)
385
+ end
386
+ end
387
+
388
+ class << self
389
+ # The unique "prototype" object from which new instances are copied.
390
+ # The fields of this instance can be modified in the class definition
391
+ # to set default values for the fields in that class. (Otherwise, defaults
392
+ # defined by the fields themselves are used.) A copy of this object is
393
+ # inherited in subclasses, which they may override using defaults and
394
+ # by writing to the initial_value object itself.
395
+ #
396
+ # If called with a block, yield the initial value object before returning
397
+ # it. Useful for customization within a class definition.
398
+ #
399
+ def initial_value # :yields: the initial value
400
+ unless @initial_value
401
+ iv = defined?(superclass.initial_value) ?
402
+ superclass.initial_value.dup : ""
403
+ if iv.length < round_byte_length
404
+ iv << "\0" * (round_byte_length - iv.length)
405
+ end
406
+
407
+ @initial_value = "" # Serves as initval while the real initval is inited
408
+ @initial_value = new(iv)
409
+ @closed = false # only creating the first _real_ instance closes.
410
+
411
+ fields.each do |field|
412
+ @initial_value.send("#{field.name}=", field.default) if field.default
413
+ end
414
+ end
415
+ yield @initial_value if block_given?
416
+ @initial_value
417
+ end
418
+
419
+ # Take +data+ (a string or BitStruct) and parse it into instances of
420
+ # the +classes+, returning them in an array. The classes can be given
421
+ # as an array or a separate arguments. (For parsing a string into a _single_
422
+ # BitStruct instance, just use the #new method with the string as an arg.)
423
+ def parse(data, *classes)
424
+ classes.flatten.map do |c|
425
+ c.new(data.slice!(0...c.round_byte_length))
426
+ end
427
+ end
428
+
429
+ # Join the given structs (array or multiple args) as a string.
430
+ # Actually, the inherited String#+ instance method is the same, as is using
431
+ # Array#join.
432
+ def join(*structs)
433
+ structs.flatten.map {|struct| struct.to_s}.join("")
434
+ end
435
+ end
436
+
437
+ # ------------------------
438
+ # :section: inspection methods
439
+ #
440
+ # ------------------------
441
+
442
+ DEFAULT_INSPECT_OPTS = {
443
+ :format => "#<%s %s>",
444
+ :field_format => "%s=%s",
445
+ :separator => ", ",
446
+ :field_name_meth => :name,
447
+ :include_rest => true,
448
+ :brackets => ["[", "]"],
449
+ :include_class => true,
450
+ :simple_format => "<%s>"
451
+ }
452
+
453
+ DETAILED_INSPECT_OPTS = {
454
+ :format => "%s:\n%s",
455
+ :field_format => "%30s = %s",
456
+ :separator => "\n",
457
+ :field_name_meth => :display_name,
458
+ :include_rest => true,
459
+ :brackets => [nil, "\n"],
460
+ :include_class => true,
461
+ :simple_format => "\n%s"
462
+ }
463
+
464
+ # A standard inspect method which does not add newlines.
465
+ def inspect(opts = DEFAULT_INSPECT_OPTS)
466
+ field_format = opts[:field_format]
467
+ field_name_meth = opts[:field_name_meth]
468
+
469
+ fields_for_inspect = fields.select {|field| field.inspectable?}
470
+ if opts[:include_rest] and (rest_field = self.class.rest_field)
471
+ fields_for_inspect << rest_field
472
+ end
473
+
474
+ ary = fields_for_inspect.map do |field|
475
+ field_format %
476
+ [field.send(field_name_meth),
477
+ field.inspect_in_object(self, opts)]
478
+ end
479
+
480
+ body = ary.join(opts[:separator])
481
+
482
+ if opts[:include_class]
483
+ opts[:format] % [self.class, body]
484
+ else
485
+ opts[:simple_format] % body
486
+ end
487
+ end
488
+
489
+ # A more visually appealing inspect method that puts each field/value on
490
+ # a separate line. Very useful when output is scrolling by on a screen.
491
+ #
492
+ # (This is actually a convenience method to call #inspect with the
493
+ # DETAILED_INSPECT_OPTS opts.)
494
+ def inspect_detailed
495
+ inspect(DETAILED_INSPECT_OPTS)
496
+ end
497
+
498
+ # ------------------------
499
+ # :section: field declaration methods
500
+ #
501
+ # ------------------------
502
+
503
+ # Define accessors for a variable length substring from the end of
504
+ # the defined fields to the end of the BitStruct. The _rest_ may behave as
505
+ # a String or as some other String or BitStruct subclass.
506
+ #
507
+ # This does not add a field, which is useful because a superclass can have
508
+ # a rest method which accesses subclass data. In particular, #rest does
509
+ # not affect the #round_byte_length class method. Of course, any data
510
+ # in rest does add to the #length of the BitStruct, calculated as a string.
511
+ # Also, _rest_ is not inherited.
512
+ #
513
+ # The +ary+ argument(s) work as follows:
514
+ #
515
+ # If a class is provided, use it for the Field class (String by default).
516
+ # If a string is provided, use it for the display_name (+name+ by default).
517
+ # If a hash is provided, use it for options.
518
+ #
519
+ # *Warning*: the rest reader method returns a copy of the field, so
520
+ # accessors on that returned value do not affect the original rest field.
521
+ #
522
+ def self.rest(name, *ary)
523
+ if @rest_field
524
+ raise ArgumentError, "Duplicate rest field: #{name.inspect}."
525
+ end
526
+
527
+ opts = parse_options(ary, name, String)
528
+ offset = round_byte_length
529
+ byte_range = offset..-1
530
+ class_eval do
531
+ field_class = opts[:field_class]
532
+ define_method name do ||
533
+ field_class.new(self[byte_range])
534
+ end
535
+
536
+ define_method "#{name}=" do |val|
537
+ self[byte_range] = val
538
+ end
539
+
540
+ @rest_field = Field.new(offset, -1, name, {
541
+ :display_name => opts[:display_name],
542
+ :rest_class => field_class
543
+ })
544
+ end
545
+ end
546
+
547
+ # Not included with the other fields, but accessible separately.
548
+ def self.rest_field; @rest_field; end
549
+ end