bit-struct 0.13.1

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